created a new :job_template to wrap all commands with. using 'bash -l -c' by default now to load the entire enviroment which should help with RVM issues.

This commit is contained in:
Javan Makhmali 2010-10-20 17:12:00 -04:00
parent 2effe0b32d
commit e66226e3ae
11 changed files with 159 additions and 82 deletions

View File

@ -1,14 +1,14 @@
== Introduction
Whenever is a Ruby gem that provides a clear syntax for defining cron jobs. It outputs valid cron syntax and can even write your crontab file for you. It is designed to work well with Rails applications and can be deployed with Capistrano. Whenever works fine independently as well.
Ryan Bates created a great Railscast about Whenever: http://railscasts.com/episodes/164-cron-in-ruby
Discussion: http://groups.google.com/group/whenever-gem
Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.
== Installation
$ sudo gem install whenever
$ gem install whenever
Or with Bundler in your Gemfile.
gem 'whenever', :require => false
== Getting started
@ -41,7 +41,7 @@ More examples on the wiki: http://wiki.github.com/javan/whenever/instructions-an
== Define your own job types
Whenever ships with three pre-defined job types: command, runner, and rake. You can define your own with <code>job_type</code>.
Whenever ships with three pre-defined job types: command, runner, and rake. You can define your own with `job_type`.
For example:
@ -51,15 +51,29 @@ For example:
awesome "party", :fun_level => "extreme"
end
Would run <code>/usr/local/bin/awesome party extreme</code> every two hours. <code>:task</code> is always replaced with the first argument, and any additional <code>:whatevers</code> are replaced with the options passed in or by variables that have been defined with <code>set</code>.
Would run `/usr/local/bin/awesome party extreme` every two hours. `:task` is always replaced with the first argument, and any additional `:whatevers` are replaced with the options passed in or by variables that have been defined with `set`.
The default job types that ship with Whenever are defined like so:
job_type :command, ":task"
job_type :runner, "cd :path && script/runner -e :environment ':task'"
job_type :rake, "cd :path && RAILS_ENV=:environment /usr/bin/env rake :task"
job_type :command, ":task :output"
job_type :rake, "cd :path && RAILS_ENV=:environment rake :task :output"
job_type :runner, "cd :path && script/runner -e :environment ':task' :output"
If a <code>:path</code> is not set it will default to the directory in which <code>whenever</code> was executed. <code>:environment</code> will default to 'production'.
If a script/rails file is detected (like in a Rails 3 app), runner will be defined to fit:
job_type :runner, "cd :path && script/rails runner -e :environment ':task' :output"
If a `:path` is not set it will default to the directory in which `whenever` was executed. `:environment` will default to 'production'. `:output` will be replaced with your output redirection settings which you can read more about here: http://github.com/javan/whenever/wiki/Output-redirection-(logging-your-cron-jobs)
All jobs are by default run with `bash -l -c 'command...'`. Among other things, this allows your cron jobs to play nice with RVM by loading the entire environment instead of cron's somewhat limited environment. Read more: http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production
You can change this by setting your own job_template.
set :job_template, "bash -l -c ':job'"
Or set the job_template to nil to have your jobs execute normally.
set :job_template, nil
== Cron output
@ -81,19 +95,13 @@ In your "config/deploy.rb" file do something like:
end
end
This will update your crontab file, leaving any existing entries unharmed. When using the <code>--update-crontab</code> option, Whenever will only update the entries in your crontab file related to the current schedule.rb file. You can replace the <code>#{application}</code> with any identifying string you'd like. You can have any number of apps deploy to the same crontab file peacefully given they each use a different identifier.
This will update your crontab file, leaving any existing entries unharmed. When using the `--update-crontab` option, Whenever will only update the entries in your crontab file related to the current schedule.rb file. You can replace the `#{application}` with any identifying string you'd like. You can have any number of apps deploy to the same crontab file peacefully given they each use a different identifier.
If you wish to simply overwrite your crontab file each time you deploy, use the <code>--write-crontab</code> option. This is ideal if you are only working with one app and every crontab entry is contained in a single schedule.rb file.
If you wish to simply overwrite your crontab file each time you deploy, use the `--write-crontab` option. This is ideal if you are only working with one app and every crontab entry is contained in a single schedule.rb file.
By mixing and matching the <code>--load-file</code> and <code>--user</code> options with your various :roles in Capistrano it is entirely possible to deploy different crontab schedules under different users to all your various servers. Get creative!
By mixing and matching the `--load-file` and `--user` options with your various :roles in Capistrano it is entirely possible to deploy different crontab schedules under different users to all your various servers. Get creative!
If you want to override a variable (like your environment) at the time of deployment you can do so with the <code>--set</code> option: http://wiki.github.com/javan/whenever/setting-variables-on-the-fly
== Credit
Whenever was created for use at Inkling (http://inklingmarkets.com) where I work. Their take on it: http://blog.inklingmarkets.com/2009/02/whenever-easy-way-to-do-cron-jobs-from.html
While building Whenever, I learned a lot by digging through the source code of Capistrano - http://github.com/jamis/capistrano
If you want to override a variable (like your environment) at the time of deployment you can do so with the `--set` option: http://wiki.github.com/javan/whenever/setting-variables-on-the-fly
== Discussion / Feedback / Issues / Bugs
@ -101,6 +109,13 @@ For general discussion and questions, please use the google group: http://groups
If you've found a genuine bug or issue, please use the Issues section on github: http://github.com/javan/whenever/issues
Ryan Bates created a great Railscast about Whenever: http://railscasts.com/episodes/164-cron-in-ruby
It's a little bit dated now, but remains a good introduction.
== Credit
Whenever was created for use at Inkling (http://inklingmarkets.com) where I work. Their take on it: http://blog.inklingmarkets.com/2009/02/whenever-easy-way-to-do-cron-jobs-from.html
== License
Copyright (c) 2009+ Javan Makhmali

View File

@ -5,17 +5,25 @@ module Whenever
def initialize(options = {})
@options = options
@at = options[:at]
@at = options.delete(:at)
@template = options.delete(:template)
@job_template = options.delete(:job_template) || ":job"
@options[:output] = Whenever::Output::Redirection.new(options[:output]).to_s if options.has_key?(:output)
@options[:environment] ||= :production
@options[:path] ||= Whenever.path
end
def output
@options[:template].dup.gsub(/:\w+/) do |key|
job = process_template(@template, @options).strip
process_template(@job_template, { :job => job }).strip
end
protected
def process_template(template, options)
template.gsub(/:\w+/) do |key|
before_and_after = [$`[-1..-1], $'[0..0]]
option = @options[key.sub(':', '').to_sym]
option = options[key.sub(':', '').to_sym]
if before_and_after.all? { |c| c == "'" }
escape_single_quotes(option)
@ -27,8 +35,6 @@ module Whenever
end
end
protected
def escape_single_quotes(str)
str.gsub(/'/) { "'\\''" }
end

View File

@ -2,7 +2,7 @@ module Whenever
class JobList
def initialize(options)
@jobs, @env, @set_variables = {}, {}, {}
@jobs, @env, @set_variables, @pre_set_variables = {}, {}, {}, {}
case options
when String
@ -25,7 +25,8 @@ module Whenever
end
def set(variable, value)
return if instance_variable_defined?("@#{variable}".to_sym)
variable = variable.to_sym
return if @pre_set_variables[variable]
instance_variable_set("@#{variable}".to_sym, value)
self.class.send(:attr_reader, variable.to_sym)
@ -60,8 +61,6 @@ module Whenever
end
def generate_cron_output
set_path_environment_variable
[environment_variables, cron_jobs].compact.join
end
@ -79,21 +78,12 @@ module Whenever
pairs.each do |pair|
next unless pair.index('=')
variable, value = *pair.split('=')
set(variable.strip.to_sym, value.strip) unless variable.blank? || value.blank?
unless variable.blank? || value.blank?
variable = variable.strip.to_sym
set(variable, value.strip)
@pre_set_variables[variable] = value
end
end
def set_path_environment_variable
return if path_should_not_be_set_automatically?
@env[:PATH] = read_path unless read_path.blank?
end
def read_path
ENV['PATH'] if ENV
end
def path_should_not_be_set_automatically?
@set_path_automatically === false || @env[:PATH] || @env["PATH"]
end
def environment_variables

View File

@ -1,3 +1,6 @@
# http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production
set :job_template, "/bin/bash -l -c ':job'"
job_type :command, ":task :output"
job_type :rake, "cd :path && RAILS_ENV=:environment rake :task --silent :output"

View File

@ -217,6 +217,7 @@ NEW_CRON
setup do
@output = Whenever.cron :set => 'environment=serious', :string => \
<<-file
set :job_template, nil
set :environment, :silly
set :path, '/my/path'
every 2.hours do
@ -234,6 +235,7 @@ NEW_CRON
setup do
@output = Whenever.cron :set => 'environment=serious&path=/serious/path', :string => \
<<-file
set :job_template, nil
set :environment, :silly
set :path, '/silly/path'
every 2.hours do
@ -251,6 +253,7 @@ NEW_CRON
setup do
@output = Whenever.cron :set => ' environment = serious& path =/serious/path', :string => \
<<-file
set :job_template, nil
set :environment, :silly
set :path, '/silly/path'
every 2.hours do
@ -268,6 +271,7 @@ NEW_CRON
setup do
@output = Whenever.cron :set => ' environment=', :string => \
<<-file
set :job_template, nil
set :environment, :silly
set :path, '/silly/path'
every 2.hours do

View File

@ -6,6 +6,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every "weekday", :at => '5:02am' do
command "blahblah"
end
@ -21,6 +22,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every "weekday", :at => %w(5:02am 3:52pm) do
command "blahblah"
end
@ -37,6 +39,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every "weekday", :at => '5:02am, 3:52pm' do
command "blahblah"
end
@ -53,6 +56,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every "weekday", :at => '5:02am, 3:02pm' do
command "blahblah"
end
@ -68,6 +72,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every "mon,wed,fri", :at => '5:02am, 3:02pm' do
command "blahblah"
end
@ -83,6 +88,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/your/path'
every "mon,wed,fri", :at => '5:02am, 3:02pm' do
runner "blahblah"
@ -99,6 +105,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/your/path'
every "mon,wed,fri", :at => '5:02am, 3:02pm' do
rake "blah:blah"
@ -115,6 +122,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every [1.month, 1.day], :at => 'january 5:02am, june 17th at 2:22pm, june 3rd at 3:33am' do
command "blahblah"
end
@ -138,6 +146,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every :reboot do
command "command_1"
command "command_2"
@ -155,6 +164,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/your/path'
every :day do
rake "blah:blah"
@ -179,6 +189,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 5.minutes, :at => 1 do
command "blahblah"
end
@ -194,6 +205,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 4.minutes, :at => 2 do
command "blahblah"
end
@ -209,6 +221,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 3.minutes, :at => 7 do
command "blahblah"
end
@ -224,6 +237,7 @@ class OutputAtTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.minutes, :at => 27 do
command "blahblah"
end

View File

@ -4,10 +4,11 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
# command
context "A plain command" do
context "A plain command with the job template set to nil" do
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah"
end
@ -19,12 +20,45 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
end
end
context "A plain command with no job template set" do
setup do
@output = Whenever.cron \
<<-file
every 2.hours do
command "blahblah"
end
file
end
should "output the command with the default job template" do
assert_match /^.+ .+ .+ .+ \/bin\/bash -l -c 'blahblah'$/, @output
end
end
context "A plain command that overrides the job_template set" do
setup do
@output = Whenever.cron \
<<-file
set :job_template, "/bin/bash -l -c ':job'"
every 2.hours do
command "blahblah", :job_template => "/bin/sh -l -c ':job'"
end
file
end
should "output the command using that job_template" do
assert_match /^.+ .+ .+ .+ \/bin\/sh -l -c 'blahblah'$/, @output
assert_no_match /bash/, @output
end
end
# runner
context "A runner with path set" do
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/my/path'
every 2.hours do
runner 'blahblah'
@ -41,6 +75,7 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/my/path'
every 2.hours do
runner "blahblah", :path => '/some/other/path'
@ -59,6 +94,7 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
File.expects(:exists?).with('/my/path/script/rails').returns(true)
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
runner 'blahblah'
end
@ -76,6 +112,7 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/my/path'
every 2.hours do
rake "blahblah"
@ -92,6 +129,7 @@ class OutputDefaultDefinedJobsTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :path, '/my/path'
every 2.hours do
rake "blahblah", :path => '/some/other/path'

View File

@ -6,6 +6,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after"
every 2.hours do
some_job "during"
@ -22,6 +23,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1 :option2"
every 2.hours do
some_job "during", :option1 => 'happy', :option2 => 'birthday'
@ -38,6 +40,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1"
set :option1, 'happy'
every 2.hours do
@ -55,6 +58,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1"
set :option1, 'global'
every 2.hours do
@ -72,6 +76,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "before :task after :option1"
every 2.hours do
some_job "during", :option2 => 'happy'
@ -90,6 +95,7 @@ class OutputDefinedJobTest < Test::Unit::TestCase
@output = Whenever.cron \
<<-file
set :job_template, nil
job_type :some_job, "cd :path && :task"
every 2.hours do
some_job 'blahblah'

View File

@ -20,37 +20,4 @@ class OutputEnvTest < Test::Unit::TestCase
end
end
context "No PATH environment variable set" do
setup do
Whenever::JobList.any_instance.expects(:read_path).at_least_once.returns('/usr/local/bin')
@output = Whenever.cron ""
end
should "add a PATH variable based on the user's PATH" do
assert_match "PATH=/usr/local/bin", @output
end
end
context "A PATH environment variable set" do
setup do
Whenever::JobList.stubs(:read_path).returns('/usr/local/bin')
@output = Whenever.cron "env :PATH, '/my/path'"
end
should "use that path and the user's PATH" do
assert_match "PATH=/my/path", @output
assert_no_match /local/, @output
end
end
context "No PATH set and instructed not to automatically load the user's path" do
setup do
@output = Whenever.cron "set :set_path_automatically, false"
end
should "not have a PATH set" do
assert_no_match /PATH/, @output
end
end
end

View File

@ -6,6 +6,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, nil
every 2.hours do
command "blahblah"
@ -23,6 +24,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, 'logfile.log'
every 2.hours do
command "blahblah"
@ -39,6 +41,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:standard => 'dev_null', :error => 'dev_err'}
end
@ -54,6 +57,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, 'logfile.log'
every 2.hours do
command "blahblah", :output => 'otherlog.log'
@ -71,6 +75,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, 'logfile.log'
every 2.hours do
command "blahblah", :output => {:error => 'dev_err', :standard => 'dev_null' }
@ -88,6 +93,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, 'logfile.log'
every 2.hours do
command "blahblah", :output => false
@ -105,6 +111,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron :set => 'output=otherlog.log', :string => \
<<-file
set :job_template, nil
set :output, 'logfile.log'
every 2.hours do
command "blahblah"
@ -122,6 +129,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, {:error => 'dev_err', :standard => 'dev_null' }
every 2.hours do
command "blahblah"
@ -138,6 +146,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, {:error => 'dev_null'}
every 2.hours do
command "blahblah"
@ -154,6 +163,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :output, {:standard => 'dev_out'}
every 2.hours do
command "blahblah"
@ -170,6 +180,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:error => 'dev_err'}
end
@ -185,6 +196,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:standard => 'dev_out'}
end
@ -200,6 +212,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:standard => nil}
end
@ -215,6 +228,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:error => nil}
end
@ -230,6 +244,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:error => nil, :standard => nil}
end
@ -245,6 +260,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:error => nil, :standard => 'my.log'}
end
@ -260,6 +276,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
every 2.hours do
command "blahblah", :output => {:error => 'my_error.log', :standard => nil}
end
@ -275,6 +292,7 @@ class OutputRedirectionTest < Test::Unit::TestCase
setup do
@output = Whenever.cron \
<<-file
set :job_template, nil
set :cron_log, "cron.log"
every 2.hours do
command "blahblah"

View File

@ -24,7 +24,6 @@ class JobTest < Test::Unit::TestCase
context "A Job with quotes" do
should "output the :task if it's in single quotes" do
job = new_job(:template => "':task'", :task => 'abc123')
assert_equal %q('abc123'), job.output
@ -52,6 +51,23 @@ class JobTest < Test::Unit::TestCase
end
end
context "A Job with a job_template" do
should "use the job template" do
job = new_job(:template => ':task', :task => 'abc123', :job_template => 'left :job right')
assert_equal 'left abc123 right', job.output
end
should "escape single quotes" do
job = new_job(:template => "before ':task' after", :task => "quote -> ' <- quote", :job_template => "left ':job' right")
assert_equal %q(left 'before '\''quote -> '\\''\\'\\'''\\'' <- quote'\'' after' right), job.output
end
should "escape double quotes" do
job = new_job(:template => 'before ":task" after', :task => 'quote -> " <- quote', :job_template => 'left ":job" right')
assert_equal %q(left "before \"quote -> \\\" <- quote\" after" right), job.output
end
end
private
def new_job(options={})