kuroko2 0.2.0

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 (338) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +15 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/images/kuroko2/avatar.png +0 -0
  6. data/app/assets/images/kuroko2/kuroko-logo-horizontal.png +0 -0
  7. data/app/assets/javascripts/kuroko2/application.js +28 -0
  8. data/app/assets/javascripts/kuroko2/bootstrap.js +2363 -0
  9. data/app/assets/javascripts/kuroko2/definition_linker.js +20 -0
  10. data/app/assets/javascripts/kuroko2/instance_linker.js +10 -0
  11. data/app/assets/javascripts/kuroko2/job_definition_stats.js +67 -0
  12. data/app/assets/javascripts/kuroko2/job_definitions.js +79 -0
  13. data/app/assets/javascripts/kuroko2/job_instances.js +88 -0
  14. data/app/assets/javascripts/kuroko2/job_timelines.js.coffee +24 -0
  15. data/app/assets/javascripts/kuroko2/narrow_job_definitions.js +30 -0
  16. data/app/assets/stylesheets/kuroko2/admin-lte.css +3402 -0
  17. data/app/assets/stylesheets/kuroko2/application.scss +86 -0
  18. data/app/assets/stylesheets/kuroko2/bootstrap.css +6799 -0
  19. data/app/assets/stylesheets/kuroko2/fonts.scss +1 -0
  20. data/app/assets/stylesheets/kuroko2/job_definitions.scss +19 -0
  21. data/app/assets/stylesheets/kuroko2/users.scss +7 -0
  22. data/app/controllers/kuroko2/api/application_controller.rb +49 -0
  23. data/app/controllers/kuroko2/api/job_instances_controller.rb +46 -0
  24. data/app/controllers/kuroko2/api/stats_controller.rb +28 -0
  25. data/app/controllers/kuroko2/application_controller.rb +43 -0
  26. data/app/controllers/kuroko2/dashboard_controller.rb +27 -0
  27. data/app/controllers/kuroko2/execution_logs_controller.rb +19 -0
  28. data/app/controllers/kuroko2/executions_controller.rb +28 -0
  29. data/app/controllers/kuroko2/job_definition_stats_controller.rb +54 -0
  30. data/app/controllers/kuroko2/job_definitions_controller.rb +100 -0
  31. data/app/controllers/kuroko2/job_instances_controller.rb +87 -0
  32. data/app/controllers/kuroko2/job_schedules_controller.rb +39 -0
  33. data/app/controllers/kuroko2/job_suspend_schedules_controller.rb +38 -0
  34. data/app/controllers/kuroko2/job_timelines_controller.rb +56 -0
  35. data/app/controllers/kuroko2/logs_controller.rb +15 -0
  36. data/app/controllers/kuroko2/sessions_controller.rb +32 -0
  37. data/app/controllers/kuroko2/stars_controller.rb +30 -0
  38. data/app/controllers/kuroko2/tokens_controller.rb +47 -0
  39. data/app/controllers/kuroko2/users_controller.rb +62 -0
  40. data/app/controllers/kuroko2/workers_controller.rb +5 -0
  41. data/app/errors/http/bad_request.rb +4 -0
  42. data/app/errors/http/forbidden.rb +4 -0
  43. data/app/errors/http/unauthorized.rb +4 -0
  44. data/app/helpers/kuroko2/application_helper.rb +8 -0
  45. data/app/helpers/kuroko2/dashboard_helper.rb +4 -0
  46. data/app/helpers/kuroko2/executions_helper.rb +4 -0
  47. data/app/helpers/kuroko2/job_definitions_helper.rb +38 -0
  48. data/app/helpers/kuroko2/job_instances_helper.rb +71 -0
  49. data/app/helpers/kuroko2/job_schedules_helper.rb +4 -0
  50. data/app/helpers/kuroko2/logs_helper.rb +4 -0
  51. data/app/helpers/kuroko2/sessions_helper.rb +4 -0
  52. data/app/helpers/kuroko2/stars_helper.rb +4 -0
  53. data/app/helpers/kuroko2/tokens_helper.rb +4 -0
  54. data/app/helpers/kuroko2/users_helper.rb +4 -0
  55. data/app/helpers/kuroko2/workers_helper.rb +4 -0
  56. data/app/jobs/kuroko2/application_job.rb +4 -0
  57. data/app/mailers/kuroko2/application_mailer.rb +6 -0
  58. data/app/mailers/kuroko2/notifications.rb +63 -0
  59. data/app/models/concerns/kuroko2/table_name_customizable.rb +16 -0
  60. data/app/models/kuroko2/admin_assignment.rb +6 -0
  61. data/app/models/kuroko2/api/application_resource.rb +10 -0
  62. data/app/models/kuroko2/api/job_instance_resource.rb +7 -0
  63. data/app/models/kuroko2/application_record.rb +5 -0
  64. data/app/models/kuroko2/execution.rb +55 -0
  65. data/app/models/kuroko2/job_definition.rb +147 -0
  66. data/app/models/kuroko2/job_definition_tag.rb +6 -0
  67. data/app/models/kuroko2/job_instance.rb +118 -0
  68. data/app/models/kuroko2/job_schedule.rb +93 -0
  69. data/app/models/kuroko2/job_suspend_schedule.rb +35 -0
  70. data/app/models/kuroko2/log.rb +3 -0
  71. data/app/models/kuroko2/memory_consumption_log.rb +49 -0
  72. data/app/models/kuroko2/memory_expectancy.rb +21 -0
  73. data/app/models/kuroko2/process_signal.rb +14 -0
  74. data/app/models/kuroko2/star.rb +6 -0
  75. data/app/models/kuroko2/tag.rb +8 -0
  76. data/app/models/kuroko2/tick.rb +12 -0
  77. data/app/models/kuroko2/token.rb +101 -0
  78. data/app/models/kuroko2/user.rb +48 -0
  79. data/app/models/kuroko2/worker.rb +12 -0
  80. data/app/views/kaminari/history/_paginator.html.slim +15 -0
  81. data/app/views/kaminari/list/_paginator.html.slim +17 -0
  82. data/app/views/kuroko2/dashboard/_taglist.html.slim +13 -0
  83. data/app/views/kuroko2/dashboard/index.html.slim +49 -0
  84. data/app/views/kuroko2/execution_logs/index.json.jbuilder +16 -0
  85. data/app/views/kuroko2/executions/index.html.slim +35 -0
  86. data/app/views/kuroko2/job_definition_stats/execution_time.json.jbuilder +13 -0
  87. data/app/views/kuroko2/job_definition_stats/index.html.slim +48 -0
  88. data/app/views/kuroko2/job_definition_stats/memory.json.jbuilder +10 -0
  89. data/app/views/kuroko2/job_definitions/_alert.html.slim +7 -0
  90. data/app/views/kuroko2/job_definitions/_form.html.slim +93 -0
  91. data/app/views/kuroko2/job_definitions/_list.html.slim +26 -0
  92. data/app/views/kuroko2/job_definitions/_search_results.html.slim +51 -0
  93. data/app/views/kuroko2/job_definitions/_taglist.html.slim +13 -0
  94. data/app/views/kuroko2/job_definitions/edit.html.slim +15 -0
  95. data/app/views/kuroko2/job_definitions/index.html.slim +11 -0
  96. data/app/views/kuroko2/job_definitions/new.html.slim +12 -0
  97. data/app/views/kuroko2/job_definitions/show.html.slim +90 -0
  98. data/app/views/kuroko2/job_instances/_instance.html.slim +42 -0
  99. data/app/views/kuroko2/job_instances/index.html.slim +58 -0
  100. data/app/views/kuroko2/job_instances/show.html.slim +19 -0
  101. data/app/views/kuroko2/job_instances/working.html.slim +37 -0
  102. data/app/views/kuroko2/job_schedules/index.html.slim +32 -0
  103. data/app/views/kuroko2/job_suspend_schedules/index.html.slim +27 -0
  104. data/app/views/kuroko2/job_timelines/dataset.json.jbuilder +11 -0
  105. data/app/views/kuroko2/job_timelines/index.html.slim +31 -0
  106. data/app/views/kuroko2/kaminari/history/_paginator.html.slim +15 -0
  107. data/app/views/kuroko2/kaminari/list/_paginator.html.slim +17 -0
  108. data/app/views/kuroko2/layouts/application.html.slim +68 -0
  109. data/app/views/kuroko2/logs/index.html.slim +33 -0
  110. data/app/views/kuroko2/notifications/executor_not_assigned.text.erb +22 -0
  111. data/app/views/kuroko2/notifications/job_failure.html.slim +21 -0
  112. data/app/views/kuroko2/notifications/job_failure.text.erb +18 -0
  113. data/app/views/kuroko2/notifications/notify_long_elapsed_time.text.erb +9 -0
  114. data/app/views/kuroko2/notifications/process_absence.text.erb +22 -0
  115. data/app/views/kuroko2/notifications/remind_failure.html.slim +21 -0
  116. data/app/views/kuroko2/notifications/remind_failure.text.erb +10 -0
  117. data/app/views/kuroko2/sessions/new.html.slim +20 -0
  118. data/app/views/kuroko2/tokens/index.html.slim +42 -0
  119. data/app/views/kuroko2/users/index.html.slim +55 -0
  120. data/app/views/kuroko2/users/show.html.slim +76 -0
  121. data/app/views/kuroko2/workers/index.html.slim +40 -0
  122. data/app/views/layouts/kuroko2/application.html.slim +72 -0
  123. data/app/views/layouts/mailer.html.erb +13 -0
  124. data/app/views/layouts/mailer.text.erb +1 -0
  125. data/bin/cleanup_old_instances.rb +9 -0
  126. data/bin/remind_failure.rb +5 -0
  127. data/config/initializers/000_kuroko2.rb +16 -0
  128. data/config/initializers/assets.rb +9 -0
  129. data/config/initializers/garage.rb +2 -0
  130. data/config/initializers/kaminari_config.rb +3 -0
  131. data/config/initializers/omniauth.rb +5 -0
  132. data/config/locales/en.yml +28 -0
  133. data/config/routes.rb +54 -0
  134. data/db/migrate/001_create_job_definitions.rb +22 -0
  135. data/db/migrate/002_create_job_instances.rb +17 -0
  136. data/db/migrate/003_create_job_schedules.rb +14 -0
  137. data/db/migrate/004_create_ticks.rb +7 -0
  138. data/db/migrate/005_create_logs.rb +13 -0
  139. data/db/migrate/006_create_tokens.rb +21 -0
  140. data/db/migrate/007_create_executions.rb +26 -0
  141. data/db/migrate/008_create_process_signals.rb +15 -0
  142. data/db/migrate/009_create_users.rb +20 -0
  143. data/db/migrate/010_create_admin_assignments.rb +12 -0
  144. data/db/migrate/011_create_stars.rb +12 -0
  145. data/db/migrate/012_create_workers.rb +15 -0
  146. data/db/migrate/018_create_job_definition_tags.rb +13 -0
  147. data/db/migrate/019_create_tags.rb +11 -0
  148. data/db/migrate/021_create_memory_expectancies.rb +17 -0
  149. data/db/migrate/025_create_job_suspend_schedules.rb +12 -0
  150. data/lib/kuroko2/command/executor.rb +62 -0
  151. data/lib/kuroko2/command/kill.rb +24 -0
  152. data/lib/kuroko2/command/monitor.rb +109 -0
  153. data/lib/kuroko2/command/shell.rb +163 -0
  154. data/lib/kuroko2/configuration.rb +19 -0
  155. data/lib/kuroko2/engine.rb +59 -0
  156. data/lib/kuroko2/execution_logger/cloud_watch_logs.rb +92 -0
  157. data/lib/kuroko2/execution_logger/void.rb +13 -0
  158. data/lib/kuroko2/execution_logger.rb +20 -0
  159. data/lib/kuroko2/memory_sampler.rb +50 -0
  160. data/lib/kuroko2/return_to_validator.rb +14 -0
  161. data/lib/kuroko2/servers/base.rb +30 -0
  162. data/lib/kuroko2/servers/command_executor.rb +27 -0
  163. data/lib/kuroko2/servers/job_scheduler.rb +25 -0
  164. data/lib/kuroko2/servers/workflow_processor.rb +25 -0
  165. data/lib/kuroko2/util/logger.rb +19 -0
  166. data/lib/kuroko2/util/rails_logger_formatter.rb +9 -0
  167. data/lib/kuroko2/version.rb +3 -0
  168. data/lib/kuroko2/workflow/assertion_error.rb +6 -0
  169. data/lib/kuroko2/workflow/engine.rb +141 -0
  170. data/lib/kuroko2/workflow/engine_error.rb +6 -0
  171. data/lib/kuroko2/workflow/node.rb +124 -0
  172. data/lib/kuroko2/workflow/notifier/concerns/chat_message_builder.rb +39 -0
  173. data/lib/kuroko2/workflow/notifier/hipchat.rb +88 -0
  174. data/lib/kuroko2/workflow/notifier/mail.rb +44 -0
  175. data/lib/kuroko2/workflow/notifier/slack.rb +121 -0
  176. data/lib/kuroko2/workflow/notifier.rb +31 -0
  177. data/lib/kuroko2/workflow/processor.rb +40 -0
  178. data/lib/kuroko2/workflow/scheduler.rb +42 -0
  179. data/lib/kuroko2/workflow/script_parser.rb +66 -0
  180. data/lib/kuroko2/workflow/shell_scanner.rb +34 -0
  181. data/lib/kuroko2/workflow/syntax_error.rb +6 -0
  182. data/lib/kuroko2/workflow/task/auto_skip_error.rb +20 -0
  183. data/lib/kuroko2/workflow/task/base.rb +32 -0
  184. data/lib/kuroko2/workflow/task/env.rb +45 -0
  185. data/lib/kuroko2/workflow/task/execute.rb +128 -0
  186. data/lib/kuroko2/workflow/task/expected_time.rb +9 -0
  187. data/lib/kuroko2/workflow/task/fork.rb +45 -0
  188. data/lib/kuroko2/workflow/task/kuroko_runner.rb +24 -0
  189. data/lib/kuroko2/workflow/task/noop.rb +13 -0
  190. data/lib/kuroko2/workflow/task/queue.rb +27 -0
  191. data/lib/kuroko2/workflow/task/rails_env.rb +24 -0
  192. data/lib/kuroko2/workflow/task/sequence.rb +13 -0
  193. data/lib/kuroko2/workflow/task/sleep.rb +29 -0
  194. data/lib/kuroko2/workflow/task/sub_process.rb +53 -0
  195. data/lib/kuroko2/workflow/task/time_base.rb +44 -0
  196. data/lib/kuroko2/workflow/task/timeout.rb +9 -0
  197. data/lib/kuroko2/workflow/task/wait.rb +143 -0
  198. data/lib/kuroko2.rb +26 -0
  199. data/lib/tasks/kuroko2_tasks.rake +4 -0
  200. data/spec/command/kill_spec.rb +20 -0
  201. data/spec/command/monitor_spec.rb +68 -0
  202. data/spec/command/shell_spec.rb +87 -0
  203. data/spec/controllers/dashboard_controller_spec.rb +5 -0
  204. data/spec/controllers/executions_controller_spec.rb +23 -0
  205. data/spec/controllers/job_definition_stats_controller_spec.rb +95 -0
  206. data/spec/controllers/job_definitions_controller_spec.rb +89 -0
  207. data/spec/controllers/job_instances_controller_spec.rb +62 -0
  208. data/spec/controllers/job_schedules_controller_spec.rb +39 -0
  209. data/spec/controllers/job_suspend_schedules_controller_spec.rb +39 -0
  210. data/spec/controllers/job_timelines_controller_spec.rb +114 -0
  211. data/spec/controllers/logs_controller_spec.rb +5 -0
  212. data/spec/controllers/sessions_controller_spec.rb +58 -0
  213. data/spec/controllers/stars_controller_spec.rb +28 -0
  214. data/spec/controllers/tokens_controller_spec.rb +5 -0
  215. data/spec/controllers/users_controller_spec.rb +51 -0
  216. data/spec/controllers/workers_controller_spec.rb +5 -0
  217. data/spec/dummy/Rakefile +6 -0
  218. data/spec/dummy/app/assets/config/manifest.js +5 -0
  219. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  220. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  221. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  222. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  223. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  224. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  225. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  226. data/spec/dummy/app/jobs/application_job.rb +2 -0
  227. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  228. data/spec/dummy/app/models/application_record.rb +3 -0
  229. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  230. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  231. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  232. data/spec/dummy/bin/bundle +3 -0
  233. data/spec/dummy/bin/rails +4 -0
  234. data/spec/dummy/bin/rake +4 -0
  235. data/spec/dummy/bin/setup +34 -0
  236. data/spec/dummy/bin/update +29 -0
  237. data/spec/dummy/config/application.rb +25 -0
  238. data/spec/dummy/config/boot.rb +3 -0
  239. data/spec/dummy/config/cable.yml +9 -0
  240. data/spec/dummy/config/database.yml +29 -0
  241. data/spec/dummy/config/environment.rb +5 -0
  242. data/spec/dummy/config/environments/development.rb +54 -0
  243. data/spec/dummy/config/environments/production.rb +86 -0
  244. data/spec/dummy/config/environments/test.rb +42 -0
  245. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  246. data/spec/dummy/config/initializers/assets.rb +11 -0
  247. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  248. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  249. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  250. data/spec/dummy/config/initializers/inflections.rb +16 -0
  251. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  252. data/spec/dummy/config/initializers/new_framework_defaults.rb +23 -0
  253. data/spec/dummy/config/initializers/session_store.rb +3 -0
  254. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  255. data/spec/dummy/config/kuroko2.yml +35 -0
  256. data/spec/dummy/config/locales/en.yml +23 -0
  257. data/spec/dummy/config/puma.rb +47 -0
  258. data/spec/dummy/config/routes.rb +3 -0
  259. data/spec/dummy/config/secrets.yml +22 -0
  260. data/spec/dummy/config/spring.rb +6 -0
  261. data/spec/dummy/config.ru +5 -0
  262. data/spec/dummy/db/schema.rb +217 -0
  263. data/spec/dummy/lib/dummy_extention.rb +14 -0
  264. data/spec/dummy/lib/kuroko2/workflow/task/custom_task1.rb +13 -0
  265. data/spec/dummy/public/404.html +67 -0
  266. data/spec/dummy/public/422.html +67 -0
  267. data/spec/dummy/public/500.html +66 -0
  268. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  269. data/spec/dummy/public/apple-touch-icon.png +0 -0
  270. data/spec/dummy/public/favicon.ico +0 -0
  271. data/spec/execution_logger/cloud_watch_logs_spec.rb +95 -0
  272. data/spec/factories/execution_factory.rb +13 -0
  273. data/spec/factories/job_definition_factory.rb +21 -0
  274. data/spec/factories/job_instance_factory.rb +4 -0
  275. data/spec/factories/job_schedule_factory.rb +10 -0
  276. data/spec/factories/job_suspend_schedule_factory.rb +8 -0
  277. data/spec/factories/memory_expectancy_factory.rb +5 -0
  278. data/spec/factories/process_signal_factory.rb +4 -0
  279. data/spec/factories/star_factory.rb +4 -0
  280. data/spec/factories/tick_factory.rb +4 -0
  281. data/spec/factories/token_factory.rb +10 -0
  282. data/spec/factories/user_factory.rb +11 -0
  283. data/spec/factories/worker_factory.rb +8 -0
  284. data/spec/features/dashborad_spec.rb +82 -0
  285. data/spec/features/job_definition_spec.rb +74 -0
  286. data/spec/features/job_instance_spec.rb +94 -0
  287. data/spec/features/sign_in_and_out_spec.rb +17 -0
  288. data/spec/features/users_spec.rb +90 -0
  289. data/spec/features/workers_spec.rb +44 -0
  290. data/spec/helpers/executions_helper_spec.rb +4 -0
  291. data/spec/helpers/job_definition_helper_spec.rb +42 -0
  292. data/spec/helpers/job_schedules_helper_spec.rb +4 -0
  293. data/spec/helpers/logs_helper_spec.rb +4 -0
  294. data/spec/helpers/tokens_helper_spec.rb +4 -0
  295. data/spec/helpers/users_helper_spec.rb +4 -0
  296. data/spec/helpers/workers_helper_spec.rb +4 -0
  297. data/spec/mailers/notifications_spec.rb +54 -0
  298. data/spec/memory_sampler_spec.rb +11 -0
  299. data/spec/models/admin_assignment_spec.rb +4 -0
  300. data/spec/models/execution_spec.rb +26 -0
  301. data/spec/models/job_definition_spec.rb +163 -0
  302. data/spec/models/job_instance_spec.rb +115 -0
  303. data/spec/models/job_schedule_spec.rb +121 -0
  304. data/spec/models/job_suspend_schedule_spec.rb +32 -0
  305. data/spec/models/memory_consumption_log_spec.rb +50 -0
  306. data/spec/models/memory_expectancy_spec.rb +26 -0
  307. data/spec/models/star_spec.rb +4 -0
  308. data/spec/models/tick_spec.rb +23 -0
  309. data/spec/models/token_spec.rb +54 -0
  310. data/spec/models/user_spec.rb +17 -0
  311. data/spec/models/worker_spec.rb +5 -0
  312. data/spec/rails_helper.rb +81 -0
  313. data/spec/requests/api/job_instances_spec.rb +96 -0
  314. data/spec/requests/api/stats_spec.rb +45 -0
  315. data/spec/return_to_validator_spec.rb +28 -0
  316. data/spec/settings_spec.rb +10 -0
  317. data/spec/spec_helper.rb +49 -0
  318. data/spec/support/feature_sign_in_helper.rb +31 -0
  319. data/spec/support/sign_in_helper.rb +5 -0
  320. data/spec/support/wait_for_ajax.rb +11 -0
  321. data/spec/workflow/engine_spec.rb +241 -0
  322. data/spec/workflow/node_spec.rb +62 -0
  323. data/spec/workflow/notifier/hipchat_spec.rb +117 -0
  324. data/spec/workflow/notifier/mail_spec.rb +86 -0
  325. data/spec/workflow/notifier/slack_spec.rb +110 -0
  326. data/spec/workflow/script_parser_spec.rb +119 -0
  327. data/spec/workflow/shell_scanner_spec.rb +47 -0
  328. data/spec/workflow/task/auto_skip_error_spec.rb +35 -0
  329. data/spec/workflow/task/env_spec.rb +47 -0
  330. data/spec/workflow/task/execute_spec.rb +127 -0
  331. data/spec/workflow/task/expected_time_spec.rb +52 -0
  332. data/spec/workflow/task/fork_spec.rb +30 -0
  333. data/spec/workflow/task/queue_spec.rb +45 -0
  334. data/spec/workflow/task/rails_env_spec.rb +30 -0
  335. data/spec/workflow/task/sleep_spec.rb +22 -0
  336. data/spec/workflow/task/sub_process_spec.rb +32 -0
  337. data/spec/workflow/task/wait_spec.rb +162 -0
  338. metadata +1038 -0
@@ -0,0 +1,147 @@
1
+ class Kuroko2::JobDefinition < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ module PreventMultiStatus
5
+ NONE = 0
6
+ WORKING_OR_ERROR = 1
7
+ WORKING = 2
8
+ ERROR = 3
9
+ end
10
+
11
+ PREVENT_TOKEN_STATUSES = {
12
+ PreventMultiStatus::NONE => [],
13
+ PreventMultiStatus::WORKING_OR_ERROR => [
14
+ Kuroko2::Token::WORKING,
15
+ Kuroko2::Token::FAILURE,
16
+ Kuroko2::Token::CRITICAL
17
+ ],
18
+ PreventMultiStatus::WORKING => [Kuroko2::Token::WORKING],
19
+ PreventMultiStatus::ERROR => [Kuroko2::Token::FAILURE, Kuroko2::Token::CRITICAL],
20
+ }
21
+
22
+ self.locking_column = :version
23
+
24
+ paginates_per 100
25
+
26
+ has_many :admin_assignments, dependent: :destroy
27
+ has_many :admins, -> { active }, through: :admin_assignments, source: :user
28
+ has_many :job_instances, -> { order(:id).reverse_order } do
29
+ def any_token?
30
+ self.any? do |instance|
31
+ instance.tokens.present?
32
+ end
33
+ end
34
+ end
35
+ has_many :job_schedules, dependent: :delete_all
36
+ has_many :job_suspend_schedules, dependent: :delete_all
37
+ has_many :job_definition_tags
38
+ has_many :tags, through: :job_definition_tags
39
+ has_one :memory_expectancy, dependent: :destroy
40
+
41
+ before_destroy :confirm_active_instances
42
+ after_initialize :set_default_values
43
+ after_save :create_default_memory_expectancy, on: :create
44
+
45
+ scope :ordered, -> { order(:id) }
46
+ scope :tagged_by, ->(tags) {
47
+ where(
48
+ id: Kuroko2::JobDefinitionTag.
49
+ where(tag_id: Kuroko2::Tag.where(name: tags).pluck(:id)).
50
+ group(:job_definition_id).
51
+ having('COUNT(1) >= ?', tags.size).
52
+ pluck(:job_definition_id)
53
+ )
54
+ }
55
+ scope :search_by, ->(query) {
56
+ column = arel_table
57
+ or_query = column[:name].matches("%#{query}%").or(column[:script].matches("%#{query}%"))
58
+
59
+ search_by_tag_definition_ids = Kuroko2::JobDefinitionTag.joins(:tag).
60
+ where('tags.name LIKE ?', "%#{query}%").distinct.pluck(:job_definition_id)
61
+
62
+ if search_by_tag_definition_ids.present?
63
+ or_query = or_query.or(column[:id].in(search_by_tag_definition_ids))
64
+ end
65
+
66
+ where(or_query)
67
+ }
68
+
69
+
70
+ validates :name, length: { maximum: 40 }, presence: true
71
+ validates :description, presence: true
72
+ validates :script, presence: true
73
+ validate :script_syntax
74
+ validate :validate_number_of_admins
75
+ validates :hipchat_additional_text, length: { maximum: 180 }
76
+ validates :slack_channel, length: { maximum: 21 }, format: {
77
+ with: /\A#[^\.\s]+\z/, allow_blank: true,
78
+ message: ' must start with # and must not include any dots or spaces'
79
+ }
80
+
81
+ def proceed_multi_instance?
82
+ tokens = Kuroko2::Token.where(job_definition_id: self.id)
83
+ (tokens.map(&:status) & PREVENT_TOKEN_STATUSES[self.prevent_multi]).empty?
84
+ end
85
+
86
+ def text_tags
87
+ tags.pluck(:name).join(',')
88
+ end
89
+
90
+ def text_tags=(text_tags)
91
+ self.tags = text_tags.gsub(/[[:blank:]]+/, '').split(/[,、]/).uniq.map do |name|
92
+ Kuroko2::Tag.find_or_create_by(name: name)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def confirm_active_instances
99
+ if job_instances.any_token?
100
+ errors.add(:base, I18n.t('model.job_definition.confirm_active_instances'))
101
+ throw :abort
102
+ end
103
+ end
104
+
105
+ def set_default_values
106
+ self.description ||= <<-EOF.strip_heredoc
107
+ An description of the job definition.
108
+
109
+ ## Failure Affects
110
+ Affected users, services and/ or business areas.
111
+
112
+ ## Workaround
113
+ Choose one of the following:
114
+ - __Retry__ as soon as possible.
115
+ - Make an urgent call to administrator (Job stays in _Error_ state)
116
+ - Do nothing, and let administrator recover later (Job stays in _Error_ state)
117
+ - Ignore error and _Cancel_ the job (No recovery required)
118
+
119
+ ## Recovery Procedures
120
+ Describe how to recover from the failure.
121
+ EOF
122
+ end
123
+
124
+ def create_default_memory_expectancy
125
+ create_memory_expectancy! unless memory_expectancy
126
+ end
127
+
128
+ def script_syntax
129
+ Kuroko2::Workflow::ScriptParser.new(script).parse
130
+
131
+ true
132
+ rescue Kuroko2::Workflow::SyntaxError => e
133
+ errors.add(:base, I18n.t('model.job_definition.script_syntax', reason: e.message))
134
+
135
+ false
136
+ rescue Kuroko2::Workflow::AssertionError => e
137
+ errors.add(:base, I18n.t('model.job_definition.validation_error', reason: e.message))
138
+
139
+ false
140
+ end
141
+
142
+ def validate_number_of_admins
143
+ if self.admins.empty?
144
+ errors.add(:admins, I18n.t('model.job_definition.validate_number_of_admins'))
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,6 @@
1
+ class Kuroko2::JobDefinitionTag < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :tag
5
+ belongs_to :job_definition
6
+ end
@@ -0,0 +1,118 @@
1
+ class Kuroko2::JobInstance < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :job_definition
5
+
6
+ has_many :logs, dependent: :delete_all do
7
+ def info(message)
8
+ add('INFO', message)
9
+ end
10
+
11
+ def warn(message)
12
+ add('WARN', message)
13
+ end
14
+
15
+ def error(message)
16
+ add('ERROR', message)
17
+ end
18
+
19
+ private
20
+ def add(level, message)
21
+ self.create(level: level, message: message)
22
+ end
23
+ end
24
+ has_many :tokens, dependent: :restrict_with_exception
25
+ has_many :executions, dependent: :restrict_with_exception
26
+ has_one :memory_consumption_log, dependent: :destroy
27
+
28
+ before_create :copy_script
29
+ after_create :generate_token
30
+
31
+ scope :working, -> { where(finished_at: nil, canceled_at: nil) }
32
+ scope :finished, -> { where.not(finished_at: nil) }
33
+
34
+ def error?
35
+ working? && error_at?
36
+ end
37
+
38
+ def working?
39
+ !finished_at? && !canceled_at?
40
+ end
41
+
42
+ def cancelable?
43
+ tokens.first.try(:cancelable?)
44
+ end
45
+
46
+ def cancel
47
+ self.tokens.destroy(*self.tokens)
48
+ self.executions.destroy(*self.executions)
49
+ self.touch(:canceled_at)
50
+ end
51
+
52
+ # Log given value if it is greater than stored one.
53
+ # This logging is not so important that we can ignore race condition,
54
+ # so we use `#update` and `#create_association` without bang here.
55
+ # @param [Intger] value
56
+ def log_memory_consumption(value)
57
+ if memory_consumption_log
58
+ max = [value, memory_consumption_log.value].max
59
+ memory_consumption_log.update(value: max)
60
+ else
61
+ create_memory_consumption_log(value: value)
62
+ end
63
+ end
64
+
65
+ def execution_minutes
66
+ (((error_at || canceled_at || finished_at || Time.now) - created_at).to_f / 60).round(2)
67
+ end
68
+
69
+ def status
70
+ if finished_at?
71
+ 'success'
72
+ elsif canceled_at?
73
+ 'canceled'
74
+ elsif error_at?
75
+ 'error'
76
+ else
77
+ 'working'
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def copy_script
84
+ self.script = job_definition.try(:script) if self.script.blank?
85
+ end
86
+
87
+ def generate_token
88
+ unless self.job_definition
89
+ raise 'No parent association is found'
90
+ end
91
+
92
+ if job_definition.proceed_multi_instance?
93
+ self.tokens << Kuroko2::Token.new do |token|
94
+ definition = self.job_definition
95
+
96
+ token.job_definition = definition
97
+ token.job_definition_version = definition.version
98
+ token.script = self.script
99
+ token.context = {
100
+ meta: {
101
+ launched_time: Time.now,
102
+ job_definition_id: definition.id,
103
+ job_definition_name: definition.name,
104
+ job_instance_id: id,
105
+ }
106
+ }
107
+ end
108
+ else
109
+ self.touch(:canceled_at)
110
+
111
+ message = 'This job was canceled because there is already a working or erred job instance.'
112
+ self.logs.warn(message)
113
+ Kuroko2.logger.warn(message)
114
+
115
+ Kuroko2::Workflow::Notifier.notify(:cancellation, self)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,93 @@
1
+ class Kuroko2::JobSchedule < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :job_definition
5
+
6
+ CRON_FORMAT = /\A
7
+ (?:[1-5]?[0-9]|(?:[1-5]?[0-9]\-[1-5]?[0-9]|\*)(?:\/[1-5]?[0-9])?)(?:,(?:[1-5]?[0-9]|(?:[1-5]?[0-9]\-[1-5]?[0-9]|\*)(?:\/[1-5]?[0-9])?))*
8
+ \s+
9
+ (?:1?[0-9]|2[0-3]|(?:(?:1?[0-9]|2[0-3])\-(?:1?[0-9]|2[0-3])|\*)(?:\/(?:1?[0-9]|2[0-3]))?)(?:,(?:1?[0-9]|2[0-3]|(?:(?:1?[0-9]|2[0-3])\-(?:1?[0-9]|2[0-3])|\*)(?:\/(?:1?[0-9]|2[0-3]))?))*
10
+ \s+
11
+ (?:(?:[1-9]|[1-2][0-9]|3[0-1])|(?:(?:[1-9]|[1-2][0-9]|3[0-1])\-(?:[1-9]|[1-2][0-9]|3[0-1])|\*)(?:\/(?:[1-9]|[1-2][0-9]|3[0-1]))?)(?:,(?:(?:[1-9]|[1-2][0-9]|3[0-1])|(?:(?:[1-9]|[1-2][0-9]|3[0-1])\-(?:[1-9]|[1-2][0-9]|3[0-1])|\*)(?:\/(?:[1-9]|[1-2][0-9]|3[0-1]))?))*
12
+ \s+
13
+ (?:(?:[1-9]|1[0-2])|(?:(?:[1-9]|1[0-2])\-(?:[1-9]|1[0-2])|\*)(?:\/(?:[1-9]|1[0-2]))?)(?:,(?:(?:[1-9]|1[0-2])|(?:(?:[1-9]|1[0-2])\-(?:[1-9]|1[0-2])|\*)(?:\/(?:[1-9]|1[0-2]))?))*
14
+ \s+
15
+ (?:[0-6]|(?:(?:[0-6]\-[0-6]|\*)(?:\/[0-6])?))(?:,(?:[0-6]|(?:(?:[0-6]\-[0-6]|\*)(?:\/[0-6])?)))*
16
+ \z/x
17
+
18
+ validates :cron, format: { with: CRON_FORMAT }, uniqueness: { scope: :job_definition_id }
19
+ validate :validate_cron_schedule
20
+
21
+ def next(now = Time.now)
22
+ if 1.month.ago(now).future?
23
+ Kuroko2.logger.warn("Exceeds the time of criteria #{now}. (Up to 1 month since)")
24
+ return
25
+ end
26
+
27
+ next_time = Chrono::Iterator.new(self.cron, now: now).next
28
+ suspend_times = suspend_times(now, next_time)
29
+
30
+ if suspend_times.include?(next_time)
31
+ self.next(next_time)
32
+ else
33
+ next_time
34
+ end
35
+ end
36
+
37
+ def scheduled_times(time_from, time_to)
38
+ it = Chrono::Iterator.new(cron, now: time_from)
39
+ scheduled_times = []
40
+
41
+ loop do
42
+ next_time = it.next
43
+ if next_time <= time_to
44
+ scheduled_times << next_time
45
+ else
46
+ break
47
+ end
48
+ end
49
+
50
+ scheduled_times
51
+ end
52
+
53
+ def suspend_times(time_from, time_to)
54
+ if job_definition && job_definition.job_suspend_schedules.present?
55
+ job_definition.job_suspend_schedules.
56
+ map { |schedule| schedule.suspend_times(time_from, time_to) }.flatten.uniq
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def validate_cron_schedule
65
+ if CRON_FORMAT === cron
66
+ self.next
67
+ end
68
+ nil
69
+ rescue Chrono::Fields::Base::InvalidField => e
70
+ errors.add(:cron, "has invalid field: #{e.message}")
71
+ end
72
+
73
+ def self.launch_scheduled_jobs!(time_from, time_to)
74
+ find_each do |schedule|
75
+ definition = schedule.job_definition
76
+ suspend_times = schedule.suspend_times(time_from, time_to)
77
+
78
+ schedule.scheduled_times(time_from, time_to).each do |time|
79
+ if definition.suspended?
80
+ Kuroko2.logger.info("Skipped suspended \"##{definition.id} #{definition.name}\" that is scheduled at #{I18n.l(time, format: :short)} by `#{schedule.cron}`")
81
+ elsif suspend_times.include?(time)
82
+ Kuroko2.logger.info("Skipped schedule suspended \"##{definition.id} #{definition.name}\" that is scheduled at #{I18n.l(time, format: :short)} by `#{schedule.cron}`")
83
+ else
84
+ message = "Launched \"##{definition.id} #{definition.name}\" that is scheduled at #{I18n.l(time, format: :short)} by `#{schedule.cron}`"
85
+ Kuroko2.logger.info(message)
86
+
87
+ instance = definition.job_instances.create
88
+ instance.logs.info(message)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,35 @@
1
+ class Kuroko2::JobSuspendSchedule < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :job_definition
5
+
6
+ validates :cron, format: { with: Kuroko2::JobSchedule::CRON_FORMAT }, uniqueness: { scope: :job_definition_id }
7
+ validate :validate_cron_schedule
8
+
9
+ def suspend_times(time_from, time_to)
10
+ it = Chrono::Iterator.new(cron, now: time_from - 1)
11
+ suspend_times = []
12
+
13
+ loop do
14
+ next_time = it.next
15
+ if next_time <= time_to
16
+ suspend_times << next_time
17
+ else
18
+ break
19
+ end
20
+ end
21
+
22
+ suspend_times
23
+ end
24
+
25
+ private
26
+
27
+ def validate_cron_schedule
28
+ if Kuroko2::JobSchedule::CRON_FORMAT === cron
29
+ Chrono::Iterator.new(self.cron).next
30
+ end
31
+ nil
32
+ rescue Chrono::Fields::Base::InvalidField => e
33
+ errors.add(:cron, "has invalid field: #{e.message}")
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ class Kuroko2::Log < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+ end
@@ -0,0 +1,49 @@
1
+ class Kuroko2::MemoryConsumptionLog < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :job_instance
5
+
6
+ validates :value, presence: true
7
+
8
+ # As count becames greater, the interval period will be longer.
9
+ # First interval will be 1 second and next interval will be 1 second too,
10
+ # then next interval will be 4 seconds then next interval will be 9 seconds.
11
+ # Finally, maximum of interval will be 30 minutes.
12
+ class Interval
13
+ INITIAL_INTERVAL_PERIOD = 1.second.to_i
14
+ MAX_INTERVAL_PERIOD = 30.minutes.to_i
15
+ INCREMENT = 2
16
+
17
+ attr_reader :base_time, :count
18
+
19
+ # @param [Time] base_time
20
+ # @param [Integer] count Throttled to be less than 50.
21
+ def initialize(base_time, count = 0)
22
+ @base_time = base_time
23
+ @count = [count, 50].min
24
+ end
25
+
26
+ # @param [Time] now
27
+ # @return [Boolean]
28
+ def reached?(now)
29
+ (now - @base_time) > current_length
30
+ end
31
+
32
+ # @return [MemoryConsumptionLog::Interval]
33
+ def next
34
+ self.class.new(Time.at(@base_time.to_i + current_length), @count.succ)
35
+ end
36
+
37
+ private
38
+
39
+ # current_length mapping for count (0..50)
40
+ #
41
+ # [1, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256,
42
+ # 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900,
43
+ # 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764,
44
+ # 1800, 1800, 1800, 1800, 1800, 1800, 1800, 1800]
45
+ def current_length
46
+ [[@count ** INCREMENT, INITIAL_INTERVAL_PERIOD].max, MAX_INTERVAL_PERIOD].min
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ class Kuroko2::MemoryExpectancy < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ DEFAULT_VALUE = 0
5
+
6
+ belongs_to :job_definition
7
+
8
+ validates :expected_value, presence: true
9
+
10
+ def memory_consumption_logs
11
+ Kuroko2::MemoryConsumptionLog.joins(:job_instance).
12
+ merge(Kuroko2::JobInstance.where(job_definition_id: job_definition_id))
13
+ end
14
+
15
+ # Calculates expected_value with latest consumption logs, then stores it,
16
+ def calculate!
17
+ if calculated_value = memory_consumption_logs.maximum(:value)
18
+ update!(expected_value: calculated_value)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ class Kuroko2::ProcessSignal < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ scope :unstarted, -> { where(started_at: nil) }
5
+ scope :on, ->(hostname) { where(hostname: hostname) }
6
+
7
+ def self.poll(hostname)
8
+ self.transaction do
9
+ unstarted.on(hostname).lock.take.tap do |signal|
10
+ signal.touch(:started_at) if signal
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ class Kuroko2::Star < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :user
5
+ belongs_to :job_definition
6
+ end
@@ -0,0 +1,8 @@
1
+ class Kuroko2::Tag < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ has_many :job_definition_tags
5
+ has_many :job_definitions, through: :job_definition_tags
6
+
7
+ validates :name, length: { maximum: 100 }
8
+ end
@@ -0,0 +1,12 @@
1
+ class Kuroko2::Tick < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ def self.fetch_then_update(now)
5
+ tick = self.first_or_create
6
+ last = tick.at || now
7
+
8
+ tick.update_column(:at, now)
9
+ last
10
+ end
11
+
12
+ end
@@ -0,0 +1,101 @@
1
+ class Kuroko2::Token < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ WORKING = 0
5
+ FINISHED = 1
6
+ FAILURE = 2
7
+ WAITING = 3
8
+ CRITICAL = 9
9
+
10
+ STATUSES = {
11
+ WORKING => :working,
12
+ FINISHED => :finished,
13
+ FAILURE => :failure,
14
+ CRITICAL => :critical,
15
+ WAITING => :waiting,
16
+ }.freeze
17
+
18
+ serialize :context, JSON
19
+
20
+ belongs_to :job_definition
21
+ belongs_to :job_instance
22
+
23
+ has_many :children, class_name: 'Token', foreign_key: 'parent_id', dependent: :destroy
24
+ belongs_to :parent, class_name: 'Token', optional: true
25
+
26
+ has_one :execution
27
+
28
+ before_create :set_default_values
29
+
30
+ scope :processable, -> { where(status: [WORKING, WAITING])}
31
+ scope :working, -> { where(status: WORKING) }
32
+ scope :finished, -> { where(status: FINISHED) }
33
+ scope :waiting, -> { where(status: WAITING) }
34
+
35
+ def working?
36
+ status == WORKING
37
+ end
38
+
39
+ def failure?
40
+ status == FAILURE
41
+ end
42
+
43
+ def finished?
44
+ status == FINISHED
45
+ end
46
+
47
+ def critical?
48
+ status == CRITICAL
49
+ end
50
+
51
+ def waiting?
52
+ status == WAITING
53
+ end
54
+
55
+ def mark_as_failure
56
+ self.status = FAILURE
57
+ end
58
+
59
+ def mark_as_critical(error)
60
+ self.status = CRITICAL
61
+ self.message = error.message
62
+ end
63
+
64
+ def mark_as_finished
65
+ self.status = FINISHED
66
+ end
67
+
68
+ def mark_as_working
69
+ self.status = WORKING
70
+ end
71
+
72
+ def mark_as_waiting
73
+ self.status = WAITING
74
+ end
75
+
76
+ def status_name
77
+ STATUSES[status].to_s
78
+ end
79
+
80
+ def cancelable?
81
+ case status
82
+ when WORKING, WAITING
83
+ children.many? && children.all? do |child|
84
+ child.status == FINISHED || child.cancelable?
85
+ end
86
+ when FAILURE
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def set_default_values
96
+ self.uuid ||= SecureRandom.uuid
97
+ self.message ||= ''
98
+ self.context ||= {}
99
+ self.status ||= WORKING
100
+ end
101
+ end
@@ -0,0 +1,48 @@
1
+ class Kuroko2::User < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ GRAVATAR_URL = '//www.gravatar.com/avatar/%s?s=90&d=mm'
5
+ GOOGLE_OAUTH2_PROVIDER = 'google_oauth2'
6
+ GROUP_PROVIDER = 'group_mail'
7
+
8
+ paginates_per 100
9
+
10
+ scope :active, -> { where(suspended_at: nil) }
11
+ scope :with, -> (ids) { where(id: ids) }
12
+ scope :group_user, -> { where(provider: GROUP_PROVIDER) }
13
+
14
+ has_many :stars
15
+ has_many :job_definitions, through: :stars
16
+
17
+ has_many :admin_assignments, dependent: :restrict_with_error
18
+ has_many :assigned_job_definitions, through: :admin_assignments, source: :job_definition
19
+
20
+ validates :name, uniqueness: { case_sensitive: false} , presence: true
21
+ validates :email, uniqueness: { case_sensitive: false}, presence: true
22
+
23
+ before_create :set_gravatar_image
24
+
25
+ def self.find_or_create_user(uid, attributes)
26
+ find_or_create_by(uid: uid) do |user|
27
+ user.name = attributes[:name]
28
+ user.email = attributes[:email]
29
+ user.first_name = attributes[:first_name]
30
+ user.last_name = attributes[:last_name]
31
+ end
32
+ end
33
+
34
+ def google_account?
35
+ self.provider == GOOGLE_OAUTH2_PROVIDER
36
+ end
37
+
38
+ private
39
+
40
+ def set_gravatar_image
41
+ self.image = gravatar_url(self.email)
42
+ end
43
+
44
+ def gravatar_url(email)
45
+ GRAVATAR_URL % Digest::MD5::hexdigest(email.strip.downcase)
46
+ end
47
+
48
+ end
@@ -0,0 +1,12 @@
1
+ class Kuroko2::Worker < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :execution, optional: true
5
+
6
+ scope :on, -> (hostname) { where(hostname: hostname) }
7
+ scope :ordered, -> { order(:hostname, :worker_id) }
8
+
9
+ def self.executing(id)
10
+ where(execution_id: id).take
11
+ end
12
+ end