kuroko2 0.2.0

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