tasks_scheduler 0.3.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +3 -1
- data/app/assets/javascripts/tasks_scheduler.js +2 -109
- data/app/assets/javascripts/tasks_scheduler/alert.js +58 -0
- data/app/assets/javascripts/tasks_scheduler/status.js +58 -0
- data/app/assets/stylesheets/tasks_scheduler.scss +4 -0
- data/app/controllers/scheduled_tasks_controller.rb +5 -4
- data/app/controllers/tasks_scheduler_daemon_controller.rb +4 -3
- data/app/controllers/tasks_scheduler_daemon_controller/_download_log.rb +6 -0
- data/app/helpers/scheduled_tasks_helper.rb +2 -0
- data/app/models/scheduled_task.rb +16 -14
- data/app/models/scheduled_task/checker.rb +17 -5
- data/app/models/scheduled_task/log.rb +9 -6
- data/app/models/scheduled_task/runner.rb +9 -5
- data/app/models/scheduled_task/status.rb +8 -4
- data/config/initializers/assets.rb +3 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20161122123828_create_scheduled_tasks.rb +5 -1
- data/db/migrate/20161123130153_add_status_attributes_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20161124200712_add_pid_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20161128163702_add_args_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20180223155025_add_last_fail_status_to_scheduled_tasks.rb +5 -1
- data/db/migrate/20180302170138_add_enabled_to_scheduled_tasks.rb +5 -1
- data/exe/tasks_scheduler +3 -1
- data/exe/tasks_scheduler_run_task +3 -1
- data/lib/tasks_scheduler.rb +4 -8
- data/lib/tasks_scheduler/app_gem.rb +18 -0
- data/lib/tasks_scheduler/checker.rb +5 -2
- data/lib/tasks_scheduler/checker/log.rb +2 -0
- data/lib/tasks_scheduler/cron_parser_patch.rb +5 -1
- data/lib/tasks_scheduler/cron_scheduling_validator.rb +3 -0
- data/lib/tasks_scheduler/daemon.rb +20 -13
- data/lib/tasks_scheduler/engine.rb +5 -0
- data/lib/tasks_scheduler/version.rb +3 -1
- data/test/dummy/Rakefile +3 -1
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/bin/bundle +3 -1
- data/test/dummy/bin/rails +3 -1
- data/test/dummy/bin/rake +2 -0
- data/test/dummy/bin/setup +3 -1
- data/test/dummy/config.ru +2 -0
- data/test/dummy/config/application.rb +3 -1
- data/test/dummy/config/boot.rb +4 -2
- data/test/dummy/config/environment.rb +3 -1
- data/test/dummy/config/environments/development.rb +2 -0
- data/test/dummy/config/environments/production.rb +2 -0
- data/test/dummy/config/environments/test.rb +2 -0
- data/test/dummy/config/initializers/assets.rb +2 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +1 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +2 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +2 -0
- data/test/dummy/config/initializers/inflections.rb +1 -0
- data/test/dummy/config/initializers/mime_types.rb +1 -0
- data/test/dummy/config/initializers/session_store.rb +2 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +2 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/fixtures/scheduled_tasks.yml +2 -0
- data/test/models/scheduled_task_test.rb +6 -4
- data/test/test_helper.rb +6 -4
- metadata +88 -32
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ScheduledTask < ActiveRecord::Base
|
2
4
|
module Log
|
3
5
|
def log_file(identifier)
|
4
6
|
unless log_identifiers.include?(identifier)
|
5
|
-
|
7
|
+
raise "Log identifier unknown: \"#{identifier}\" (Valid: #{log_identifiers})"
|
6
8
|
end
|
9
|
+
|
7
10
|
Rails.root.join('log', 'tasks_scheduler', "#{id}_#{identifier}.log")
|
8
11
|
end
|
9
12
|
|
@@ -24,11 +27,11 @@ class ScheduledTask < ActiveRecord::Base
|
|
24
27
|
|
25
28
|
def log_on_end(exception)
|
26
29
|
running_log = log_file(LOG_RUNNING)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
return unless ::File.exist?(running_log)
|
31
|
+
|
32
|
+
target_log = exception ? log_file(LOG_UNSUCCESSFUL) : log_file(LOG_SUCCESSFUL)
|
33
|
+
File.unlink(target_log) if File.exist?(target_log)
|
34
|
+
File.rename(running_log, target_log)
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ScheduledTask < ActiveRecord::Base
|
2
4
|
module Runner
|
3
5
|
def run
|
4
6
|
log_on_start
|
5
7
|
run_banner
|
6
8
|
return if process_running? && pid != Process.pid
|
9
|
+
|
7
10
|
status_on_start
|
8
11
|
exception = invoke_task
|
9
12
|
on_end_running(exception, STATUS_FAILED)
|
@@ -33,7 +36,7 @@ class ScheduledTask < ActiveRecord::Base
|
|
33
36
|
|
34
37
|
def run_banner
|
35
38
|
run_log("Task: #{self}")
|
36
|
-
run_log("PID: #{pid
|
39
|
+
run_log("PID: #{pid || '-'} (Current: #{Process.pid})")
|
37
40
|
run_log("Process running? #{process_running? ? 'Yes' : 'No'}")
|
38
41
|
run_log("Rails.env: #{Rails.env}")
|
39
42
|
end
|
@@ -44,15 +47,16 @@ class ScheduledTask < ActiveRecord::Base
|
|
44
47
|
Rake::Task.clear
|
45
48
|
Rails.application.load_tasks
|
46
49
|
Rake::Task[task].invoke(*invoke_args)
|
47
|
-
rescue StandardError =>
|
48
|
-
run_log(
|
49
|
-
exception =
|
50
|
+
rescue StandardError => e
|
51
|
+
run_log(e, :fatal)
|
52
|
+
exception = e
|
50
53
|
end
|
51
54
|
exception
|
52
55
|
end
|
53
56
|
|
54
57
|
def invoke_args
|
55
|
-
return []
|
58
|
+
return [] if args.blank?
|
59
|
+
|
56
60
|
args.split('|')
|
57
61
|
end
|
58
62
|
end
|
@@ -1,10 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ScheduledTask < ActiveRecord::Base
|
2
4
|
module Status
|
3
5
|
def status
|
4
6
|
return STATUS_DISABLED unless enabled?
|
5
7
|
return STATUS_RUNNING if running?
|
6
8
|
return STATUS_WAITING if waiting?
|
7
|
-
|
9
|
+
|
10
|
+
last_fail_status.presence || STATUS_FAILED
|
8
11
|
end
|
9
12
|
|
10
13
|
def failed?
|
@@ -17,6 +20,7 @@ class ScheduledTask < ActiveRecord::Base
|
|
17
20
|
|
18
21
|
def waiting?
|
19
22
|
return true if ended?(last_run_successful_end, last_run_unsuccessful_end)
|
23
|
+
|
20
24
|
status_attributes.all? { |a| send(a).blank? }
|
21
25
|
end
|
22
26
|
|
@@ -27,11 +31,11 @@ class ScheduledTask < ActiveRecord::Base
|
|
27
31
|
end
|
28
32
|
|
29
33
|
def status_on_start
|
30
|
-
|
34
|
+
update!(last_run_start: Time.zone.now)
|
31
35
|
end
|
32
36
|
|
33
37
|
def status_on_end(exception, last_fail_status)
|
34
|
-
|
38
|
+
update!(
|
35
39
|
next_run: calculate_next_run,
|
36
40
|
(exception ? :last_run_unsuccessful_start : :last_run_successful_start) => last_run_start,
|
37
41
|
(exception ? :last_run_unsuccessful_end : :last_run_successful_end) => Time.zone.now,
|
@@ -42,7 +46,7 @@ class ScheduledTask < ActiveRecord::Base
|
|
42
46
|
end
|
43
47
|
|
44
48
|
def status_attributes
|
45
|
-
%w
|
49
|
+
%w[start successful_start successful_end unsuccessful_start unsuccessful_end].map do |a|
|
46
50
|
"last_run_#{a}"
|
47
51
|
end
|
48
52
|
end
|
data/config/routes.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
create_table :scheduled_tasks do |t|
|
4
8
|
t.string :scheduling
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddStatusAttributesToScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
add_column :scheduled_tasks, :last_run_start, :datetime
|
4
8
|
add_column :scheduled_tasks, :last_run_successful_start, :datetime
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddPidToScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
add_column :scheduled_tasks, :pid, :integer
|
4
8
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddArgsToScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
add_column :scheduled_tasks, :args, :string
|
4
8
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddLastFailStatusToScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
add_column :scheduled_tasks, :last_fail_status, :string
|
4
8
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddEnabledToScheduledTasks < (
|
4
|
+
Rails.version < '5.2' ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
5
|
+
)
|
2
6
|
def change
|
3
7
|
add_column :scheduled_tasks, :enabled, :boolean, null: false, default: true
|
4
8
|
end
|
data/exe/tasks_scheduler
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'rubygems'
|
4
5
|
require 'daemons'
|
5
6
|
require 'fileutils'
|
6
7
|
|
7
8
|
def find_rails_root(dir = Dir.pwd)
|
8
|
-
|
9
|
+
raise 'Rails root not found' if ['', '/', '.'].include?(dir)
|
9
10
|
return dir if File.exist?(File.expand_path('config/environment.rb', dir))
|
11
|
+
|
10
12
|
rails_root(File.dirname(dir))
|
11
13
|
end
|
12
14
|
|
@@ -1,11 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'rubygems'
|
4
5
|
require 'daemons'
|
5
6
|
|
6
7
|
def find_rails_root(dir = Dir.pwd)
|
7
|
-
|
8
|
+
raise 'Rails root not found' if ['', '/', '.'].include?(dir)
|
8
9
|
return dir if File.exist?(File.expand_path('config/environment.rb', dir))
|
10
|
+
|
9
11
|
rails_root(File.dirname(dir))
|
10
12
|
end
|
11
13
|
|
data/lib/tasks_scheduler.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
require 'parse-cron'
|
5
|
-
require 'tasks_scheduler/cron_scheduling_validator'
|
6
|
-
require 'tasks_scheduler/cron_parser_patch'
|
7
|
-
require 'tasks_scheduler/checker'
|
8
|
-
require 'tasks_scheduler/daemon'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_ruby_utils/require_sub'
|
9
4
|
|
10
5
|
module TasksScheduler
|
6
|
+
::EacRubyUtils.require_sub __FILE__
|
11
7
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_ruby_gems_utils/gem'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module TasksScheduler
|
7
|
+
class AppGem < ::SimpleDelegator
|
8
|
+
include ::Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super(::EacRubyGemsUtils::Gem.new(::Rails.root))
|
12
|
+
end
|
13
|
+
|
14
|
+
def bundle(*args)
|
15
|
+
super(*args).chdir_root.envvar('RAILS_ENV', ::Rails.env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'tasks_scheduler/checker/log'
|
2
4
|
|
3
5
|
module TasksScheduler
|
@@ -5,8 +7,8 @@ module TasksScheduler
|
|
5
7
|
include Singleton
|
6
8
|
|
7
9
|
CHECK_INTERVAL = 15
|
8
|
-
LOG_ON_FILE_ENV_KEY = 'TASKS_SCHEDULER_LOG_ON_FILE'
|
9
|
-
LOGS_KEYS = %w
|
10
|
+
LOG_ON_FILE_ENV_KEY = 'TASKS_SCHEDULER_LOG_ON_FILE'
|
11
|
+
LOGS_KEYS = %w[rails stdout stderr].freeze
|
10
12
|
|
11
13
|
def run
|
12
14
|
check_log
|
@@ -40,6 +42,7 @@ CODE
|
|
40
42
|
|
41
43
|
def check_log
|
42
44
|
return unless log_on_file?
|
45
|
+
|
43
46
|
::Rails.logger = ::Logger.new(rails_log.path)
|
44
47
|
$stdout.reopen(stdout_log.path, 'w')
|
45
48
|
$stderr.reopen(stderr_log.path, 'w')
|
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parse-cron'
|
4
|
+
|
1
5
|
module TasksScheduler
|
2
6
|
module CronParserPatch
|
3
7
|
class TasksSchedulerTimeSource
|
4
8
|
class << self
|
5
|
-
def local(year, month, day, hour, min, second)
|
9
|
+
def local(year, month, day, hour, min, second) # rubocop:disable Metrics/ParameterLists
|
6
10
|
Time.utc(year, month, day, hour, min, second)
|
7
11
|
end
|
8
12
|
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TasksScheduler
|
2
4
|
class CronSchedulingValidator < ActiveModel::EachValidator
|
3
5
|
def validate_each(record, attribute, value)
|
4
6
|
return if value_valid?(value)
|
7
|
+
|
5
8
|
record.errors[attribute] << (options[:message] ||
|
6
9
|
I18n.translate(:cron_scheduling_validator_error_message))
|
7
10
|
end
|
@@ -1,28 +1,35 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tasks_scheduler/app_gem'
|
2
4
|
|
3
5
|
module TasksScheduler
|
4
6
|
class Daemon
|
5
|
-
ACTIONS = %w
|
7
|
+
ACTIONS = %w[status start stop restart].freeze
|
6
8
|
|
7
9
|
class << self
|
8
|
-
def
|
10
|
+
def daemon_command(action)
|
9
11
|
raise "Action not allowed: #{action} (Allowed: #{ACTIONS})" unless ACTIONS.include?(action)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
|
13
|
+
::TasksScheduler::AppGem.instance.bundle('exec', 'tasks_scheduler', action)
|
14
|
+
.envvar(::TasksScheduler::Checker::LOG_ON_FILE_ENV_KEY, '1')
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(action)
|
18
|
+
command = daemon_command(action)
|
19
|
+
result = command.execute
|
20
|
+
{
|
21
|
+
action: action, env_args: env_args_to_s(command), command: command.to_s,
|
22
|
+
status: result.fetch(:exit_code), stdout: result.fetch(:stdout),
|
23
|
+
stderr: result.fetch(:stderr)
|
24
|
+
}
|
18
25
|
end
|
19
26
|
|
20
27
|
def running?
|
21
28
|
execute('status')[:status].zero?
|
22
29
|
end
|
23
30
|
|
24
|
-
def
|
25
|
-
{
|
31
|
+
def env_args_to_s(command)
|
32
|
+
command.send(:extra_options).fetch(:envvars).map { |k, v| "#{k}=#{v}" }.join(' | ')
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
data/test/dummy/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
4
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('config/application', __dir__)
|
5
7
|
|
6
8
|
Rails.application.load_tasks
|
data/test/dummy/bin/bundle
CHANGED
data/test/dummy/bin/rails
CHANGED
data/test/dummy/bin/rake
CHANGED
data/test/dummy/bin/setup
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'pathname'
|
3
5
|
|
4
6
|
# path to your application root.
|
5
|
-
APP_ROOT = Pathname.new File.expand_path('
|
7
|
+
APP_ROOT = Pathname.new File.expand_path('..', __dir__)
|
6
8
|
|
7
9
|
Dir.chdir APP_ROOT do
|
8
10
|
# This script is a starting point to setup your application.
|