Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sequential job definitions #854

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
command "/usr/bin/my_great_command"
end

every 3.hours, sequential: true do
# Jobs in this block that don't set their own sequence will default to run in the same sequence (not in parallel)
runner "MyModel.some_process", halt_sequence_on_failure: true # If this job fails, subsequent jobs will not run (defaults to false)
rake "my:rake:task"
command "/usr/bin/my_great_command", sequence: nil # This job runs in parallel with other jobs that are not part of a sequence
runner "MyModel.task_to_run_at_four_thirty_in_the_morning", sequence: 'other' # This job runs sequentially with other jobs in the sequence called 'other'
end

every 1.day, at: '4:30 am' do
runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end
Expand Down
1 change: 1 addition & 0 deletions lib/whenever.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'whenever/numeric'
require 'whenever/numeric_seconds'
require 'whenever/job_list'
require 'whenever/job_sequence'
require 'whenever/job'
require 'whenever/command_line'
require 'whenever/cron'
Expand Down
4 changes: 3 additions & 1 deletion lib/whenever/job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

module Whenever
class Job
attr_reader :at, :roles, :mailto
attr_reader :at, :roles, :mailto, :sequence, :halt_sequence_on_failure

def initialize(options = {})
@options = options
@at = options.delete(:at)
@template = options.delete(:template)
@mailto = options.fetch(:mailto, :default_mailto)
@job_template = options.delete(:job_template) || ":job"
@sequence = options.delete(:sequence)
@halt_sequence_on_failure = options.delete(:halt_sequence_on_failure)
@roles = Array(options.delete(:roles))
@options[:output] = options.has_key?(:output) ? Whenever::Output::Redirection.new(options[:output]).to_s : ''
@options[:environment_variable] ||= "RAILS_ENV"
Expand Down
21 changes: 17 additions & 4 deletions lib/whenever/job_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def env(variable, value)
def every(frequency, options = {})
@current_time_scope = frequency
@options = options

if @options[:sequential]
@default_sequence_id ||= 0
@options[:sequence] ||= "default_sequence_#{@default_sequence_id += 1}"
end

yield
end

Expand All @@ -66,7 +72,7 @@ def job_type(name, template)

@jobs[options.fetch(:mailto)] ||= {}
@jobs[options.fetch(:mailto)][@current_time_scope] ||= []
@jobs[options.fetch(:mailto)][@current_time_scope] << Whenever::Job.new(@options.merge(@set_variables).merge(options))
@jobs[options.fetch(:mailto)][@current_time_scope] << Whenever::Job.new(@set_variables.merge(@options).merge(options))
end
end
end
Expand Down Expand Up @@ -138,10 +144,17 @@ def combine(entries)
def cron_jobs_of_time(time, jobs)
shortcut_jobs, regular_jobs = [], []

filtered_jobs = jobs.select do |job|
roles.empty? || roles.any? { |r| job.has_role?(r) }
end

grouped_jobs = filtered_jobs.group_by(&:sequence)
grouped_jobs.each do |sequence, jobs|
grouped_jobs[sequence] = JobSequence.new(jobs) if sequence
end
jobs = grouped_jobs.values.flatten

jobs.each do |job|
next unless roles.empty? || roles.any? do |r|
job.has_role?(r)
end
Whenever::Output::Cron.output(time, job, :chronic_options => @chronic_options) do |cron|
cron << "\n\n"

Expand Down
35 changes: 35 additions & 0 deletions lib/whenever/job_sequence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'shellwords'

module Whenever
class JobSequence
attr_reader :at, :roles, :mailto

def initialize(jobs, options = {})
validate!(jobs)

@jobs = jobs
@options = options
@at = options.fetch(:at, primary_job.at)
@mailto = options.fetch(:mailto, primary_job.mailto || :default_mailto)
@roles = Array(options.delete(:roles), *primary_job.roles)
end

def output
@jobs.map { |job| [job.output, job.halt_sequence_on_failure ? ' && ' : ' ; '] }.flatten[0..-2].join
end

def has_role?(role)
roles.empty? || roles.include?(role)
end

private

def primary_job
@jobs.first
end

def validate!(jobs)
raise ArgumentError, "Jobs in a sequence don't support different `at` values" if jobs.map(&:at).uniq.count > 1
end
end
end
28 changes: 28 additions & 0 deletions test/functional/output_defined_job_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ class OutputDefinedJobTest < Whenever::TestCase
assert_match(/^.+ .+ .+ .+ before during after local$/, output)
end

test "defined job with a :task and an option where the option is set globally and on the group" do
output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1"
set :option1, 'global'
every 2.hours, :option1 => 'group' do
some_job "during"
end
file

assert_match(/^.+ .+ .+ .+ before during after group$/, output)
end

test "defined job with a :task and an option where the option is set globally, on the group, and locally" do
output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1"
set :option1, 'global'
every 2.hours, :option1 => 'group' do
some_job "during", :option1 => 'local'
end
file

assert_match(/^.+ .+ .+ .+ before during after local$/, output)
end

test "defined job with a :task and an option that is not set" do
output = Whenever.cron \
<<-file
Expand Down
151 changes: 151 additions & 0 deletions test/functional/output_jobs_with_sequence_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
require 'test_helper'

class OutputJobsWithSequenceTest < Whenever::TestCase
test "defined jobs with a sequence argument specified per-job" do
output = Whenever.cron \
<<-file
every 2.hours do
command "blahblah", sequence: 'backups'
command "foofoo", sequence: 'backups'
command "barbar"
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' ; /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'barbar'", output_without_empty_line.shift
end

test "defined jobs with a sequence argument specified on the group" do
output = Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah"
command "foofoo"
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' ; /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
end

test "defined jobs with a sequence argument and a job that halts the sequence on failure" do
output = Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah", halt_sequence_on_failure: true
command "foofoo"
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' && /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
end

test "defined jobs with a sequences specified on the group and jobs" do
output = Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah"
command "barbar", sequence: nil
command "foofoo"
command "bazbaz", sequence: 'bees'
command "buzzbuzz", sequence: 'bees'
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' ; /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'barbar'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'bazbaz' ; /bin/bash -l -c 'buzzbuzz'", output_without_empty_line.shift
end

test "defined jobs with a multiple groups with sequences specified on the group and jobs" do
output = Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah"
command "barbar", sequence: nil
command "bazbaz", sequence: 'bees'
end

every 2.hours, sequence: 'backups' do
command "foofoo"
command "buzzbuzz", sequence: 'bees'
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' ; /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'barbar'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'bazbaz' ; /bin/bash -l -c 'buzzbuzz'", output_without_empty_line.shift
end

test "defined jobs with a multiple groups with sequences specified on the group and jobs" do
output = Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah"
command "barbar", sequence: nil
command "bazbaz", sequence: 'bees'
end

every 3.hours, sequence: 'backups' do
command "foofoo"
command "buzzbuzz", sequence: 'bees'
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'barbar'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'bazbaz'", output_without_empty_line.shift
assert_equal three_hours + " /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
assert_equal three_hours + " /bin/bash -l -c 'buzzbuzz'", output_without_empty_line.shift
end

test "defined jobs with a multiple groups with sequences specified on the group and jobs" do
assert_raises ArgumentError do
Whenever.cron \
<<-file
every 2.hours, sequence: 'backups' do
command "blahblah", at: 1
command "barbar", at: 2
end
file
end
end

def three_hours
"0 0,3,6,9,12,15,18,21 * * *"
end
end

class OutputJobsWithSequentialTest < Whenever::TestCase
test "defined jobs with a sequential argument" do
output = Whenever.cron \
<<-file
every 2.hours, sequential: true do
command "blahblah"
command "foofoo"
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah' ; /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
end

test "defined jobs with a sequential argument" do
output = Whenever.cron \
<<-file
every 2.hours, sequential: true do
command "blahblah"
command "foofoo", sequence: false
end
file

output_without_empty_line = lines_without_empty_line(output.lines)
assert_equal two_hours + " /bin/bash -l -c 'blahblah'", output_without_empty_line.shift
assert_equal two_hours + " /bin/bash -l -c 'foofoo'", output_without_empty_line.shift
end
end
1 change: 0 additions & 1 deletion test/unit/job_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ class JobTest < Whenever::TestCase
end
end


class JobWithQuotesTest < Whenever::TestCase
should "output the :task if it's in single quotes" do
job = new_job(:template => "':task'", :task => 'abc123')
Expand Down