did_craken 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ = Craken
2
+
3
+ Craken is a rails plugin for managing and installing rake-centric crontab files
4
+ over Capistrano.
5
+
6
+ What is a crontab? A command list that cron will run at specified intervals.
7
+ What is cron? A daemon to execute scheduled commands.
8
+
9
+ If you don't know these things already you should probably do a little research
10
+ before using this tool :) Begin here: http://en.wikipedia.org/wiki/Cron
11
+
12
+ Craken uses files called +raketab+s that are similar in every respect to crontab
13
+ files except the commands are rake tasks instead of arbitrary unix commands. The file
14
+ goes in the craken directory inside config (i.e. #{RAILS_ROOT}/config/craken/raketab).
15
+
16
+ Why not just let it run arbitrary commands instead of limiting it to rake tasks?
17
+ The main issue this plugin attempts to alleviate is having to manage the
18
+ rails environment configuration for multiple deployments. Craken will set up the
19
+ crontab to change into the application's current directory and set rake to run in
20
+ the correct environment.
21
+
22
+ Raketab files can also handle ERB escaped text bound to the rake environment.
23
+
24
+ A Capistrano file is also included to make it easy to install cron jobs on multiple
25
+ hosts. A new :cron capistrano role is necessary to define which machines to install to.
26
+
27
+ == Configuration
28
+
29
+ Command line configuration options to the rake task (defaults are in parens):
30
+ [+deploy_path+] Where the application is deployed (RAILS_ROOT)
31
+ [+crontab_exe+] Where to find the crontab excutable (/usr/bin/crontab)
32
+ [+rake_exe+] The rake executable (/usr/bin/rake)
33
+ [+raketab_rails_env+] Rails environment mode to set the cron jobs to run in (RAILS_ENV)
34
+ [+app_name+] Name of the application (tries to figure it out from the deploy path)
35
+ [+raketab_file+] Where the raketab file is:
36
+
37
+ #{RAILS_ROOT}/config/craken/#{HOSTNAME}_raketab or
38
+ #{RAILS_ROOT}/config/craken/raketab if the first is not found
39
+
40
+ The +raketab_file+ can be a crontab file (no extension), a .yaml/.yml file or
41
+ an .rb file (see Other Formats below).
42
+
43
+ == Example
44
+
45
+ An example line from an example raketab file:
46
+
47
+ 59 * * * * thing:to_do > /tmp/thing_to_do.log 2>&1
48
+
49
+ This will run the rake task thing:to_do every 59th minute after every hour.
50
+ The little redirect thing is added for effect; otherwise output will be mailed
51
+ to the user who the crontab is installed on.
52
+
53
+ When craken:install is run on the production box, the crontab will look like this:
54
+
55
+ ### foo raketab
56
+ 59 * * * * cd /home/thatguy/u/apps/foo/current && /usr/bin/rake --silent RAILS_ENV=production thing:to_do > /tmp/thing_to_do.log 2>&1
57
+ ### foo raketab end
58
+
59
+ The "magic" part of craken is the bit that crontab added:
60
+
61
+ cd /home/thatguy/u/apps/foo/current && /usr/bin/rake --silent RAILS_ENV=production
62
+
63
+ === Special crontab string support
64
+
65
+ An example line to add a rake task to be executed after reboot:
66
+
67
+ @reboot thinking_sphinx:start > /tmp/ts_startup.log 2>&1
68
+
69
+ All special strings and their effects:
70
+ [<tt>@reboot</tt>] execute after reboot
71
+ [<tt>@yearly</tt>] every year
72
+ [<tt>@annually</tt>] every year hipster version
73
+ [<tt>@monthly</tt>] every month, or better known as: <i>No roll in the hay for you this time honey!</i>
74
+ [<tt>@weekly</tt>] just like every month but weekly
75
+ [<tt>@daily</tt>] like brushing your teeth
76
+ [<tt>@midnight</tt>] when the ghosts come out
77
+ [<tt>@hourly</tt>] whenever the long hand of your watch is at 12
78
+
79
+ Read more about crontab special strings here: http://unixhelp.ed.ac.uk/CGI/man-cgi?crontab+5
80
+ == Other Formats
81
+
82
+ You can also specify other types of files to describe +raketabs+. You can have multiple formats and they
83
+ will be aggregated together.
84
+
85
+ === YAML
86
+
87
+ You can specify it in YAML. For instance in a +config/craken/raketab.yml+:
88
+
89
+ my_command:
90
+ hour: 5
91
+ minute: 30
92
+ day: 1
93
+ month: September
94
+ command: thing:to_do > /tmp/thing_to_do.log 2>&1
95
+
96
+ Here we've specified my_command to run at 5:30 AM on September 1st. Aside from the command to run,
97
+ the timings have the following defaults: 0 minute, 0 hour, every day, every month, every weekday. So
98
+ you could specify in your raketab.yml:
99
+
100
+ my_other_command:
101
+ command: other:thing:to_do > /tmp/thing_to_do.log 2>&1
102
+
103
+ This will run at midnight of every day. You can also specify weekday:
104
+
105
+ my_third_command:
106
+ weekday: thursday
107
+ command: third:thing:to_do > /tmp/thing_to_do.log 2>&1
108
+
109
+ You can also specify the month number or weekday number as you would in a crontab. Lastly, you can specify ranges or full match strings and they will be passed on to the crontab directly:
110
+
111
+ and_finally:
112
+ hour: *
113
+ weekday: 1-5
114
+ command: last:thing:to_do > /tmp/thing_to_do.log 2>&1
115
+
116
+ Here, the command will run every hour from Monday to Friday. Month and Weekday names are not supported when doing ranges in this fashion.
117
+
118
+ === Raketab DSL
119
+
120
+ The other format is written in pure ruby using the +Raketab+ object. In your +config/craken/raketab.rb+ file:
121
+
122
+ Raketab.new do |cron|
123
+ cron.schedule 'thing:to_do > /tmp/thing_to_do.log 2>&1', :every => :thursday
124
+ end
125
+
126
+ The defaults are like those for the YAML file, so in this case, thing to do will run at midnight on thursdays.
127
+
128
+ Raketab.new do |cron|
129
+ cron.schedule 'thing:to_do > /tmp/thing_to_do.log 2>&1', :on => :september, :the => '1st', :at => '5:30'
130
+ end
131
+
132
+ Here it will run only on the first of september at 5:30 AM. Month and weekday names are also supported, and
133
+ also the named units (hour, minute, month, day, weekday):
134
+
135
+ Raketab.new do |cron|
136
+ cron.schedule 'thing:to_do > /tmp/thing_to_do.log 2>&1', :on => september, :every => thursday
137
+ end
138
+
139
+ Here it will run every Thursday in September. Ranges (inclusive and exclusive), comma separated lists, in strings or using numbers or weekday and month names are also supported and their three letter abbreviations:
140
+
141
+ Raketab.new do |cron|
142
+ cron.schedule 'thing:to_do > /tmp/thing_to_do.log 2>&1', :every => mon..fri
143
+ cron.schedule 'first:five:days > /tmp/thing_to_do.log 2>&1', :days => [1,2,3,4,5]
144
+ cron.schedule 'first:day:q1 > /tmp/thing_to_do.log 2>&1', :the => '1st', :in => [jan,feb,mar]
145
+ cron.schedule 'first:day:q4 > /tmp/thing_to_do.log 2>&1', :the => '1st', :months => 'October,November,December'
146
+ end
147
+
148
+ You can find all the variants in the spec for Raketab and pick and choose which is most natural for you.
149
+
150
+ = Running the Capistrano Script
151
+
152
+ cap craken:install
153
+
154
+ Deploying to someplace other than production? Rails environment is important so rake
155
+ is set in the right mode when the cron job runs:
156
+
157
+ cap -c rails_env=qa craken:install
158
+
159
+ Different raketab files can be used to limit a subset of rake tasks to a particular
160
+ machine listed as a :cron role. These files are prefixed with the name of the machine
161
+ they need to be deployed to:
162
+
163
+ foo_raketab # goes on the "foo" machine
164
+ bar_raketab # goes on the "bar" machine
165
+ raketab # goes on all machines except "foo" and "bar"
166
+
167
+ Doug McInnes <doug.mcinnes@latimes.com>
168
+ John Dewey <john.dewey@latimes.com>
169
+ Reid MacDonald <reid.macdonald@latimes.com>
170
+ Copyright (c) 2008 Los Angeles Times, released under the MIT license
@@ -0,0 +1,112 @@
1
+ require 'socket'
2
+ require "#{File.dirname(__FILE__)}/raketab"
3
+
4
+ module Craken
5
+ def self.determine_raketab_files
6
+ if File.directory?("#{DEPLOY_PATH}/config/craken/") # Use hostname specific raketab first.
7
+ raketabs = Dir["#{DEPLOY_PATH}/config/craken/*raketab*"].partition {|f| f =~ %r[/raketab.*$] }
8
+ raketabs.last.empty? ? raketabs.first : raketabs.last.grep(/#{HOSTNAME}_raketab/)
9
+ else
10
+ Dir["#{DEPLOY_PATH}/config/raketab*"]
11
+ end
12
+ end
13
+
14
+ HOSTNAME = Socket.gethostname.split('.').first.downcase.strip
15
+ DEPLOY_PATH = ENV['deploy_path'] || RAILS_ROOT
16
+ RAKETAB_FILES = ENV['raketab_files'].split(":") rescue determine_raketab_files
17
+ CRONTAB_EXE = ENV['crontab_exe'] || "/usr/bin/crontab"
18
+ RAKE_EXE = ENV['rake_exe'] || ((rake = `which rake`.strip and rake.empty?) ? "/usr/bin/rake" : rake)
19
+ RAKETAB_RAILS_ENV = ENV['raketab_rails_env'] || RAILS_ENV
20
+ # assumes root of app is name of app, also takes into account
21
+ # capistrano deployments
22
+ APP_NAME = ENV['app_name'] || (DEPLOY_PATH =~ /\/([^\/]*)\/releases\/\d*$/ ? $1 : File.basename(DEPLOY_PATH))
23
+
24
+ # see here: http://unixhelp.ed.ac.uk/CGI/man-cgi?crontab+5
25
+ SPECIAL_STRINGS = %w[@reboot @yearly @annually @monthly @weekly @daily @midnight @hourly]
26
+
27
+ CRONTAB_MARKER_START = "### #{APP_NAME} #{RAKETAB_RAILS_ENV} raketab start"
28
+ CRONTAB_MARKER_END = "### #{APP_NAME} #{RAKETAB_RAILS_ENV} raketab end"
29
+ # strip out the existing raketab cron tasks for this project
30
+ def load_and_strip
31
+ crontab = ''
32
+ old = false
33
+ `#{CRONTAB_EXE} -l`.each_line do |line|
34
+ line.strip!
35
+ if old || line == CRONTAB_MARKER_START
36
+ old = line != CRONTAB_MARKER_END
37
+ else
38
+ crontab << line
39
+ crontab << "\n"
40
+ end
41
+ end
42
+ crontab
43
+ end
44
+
45
+ def append_tasks(crontab, raketab)
46
+ crontab << "#{CRONTAB_MARKER_START}\n"
47
+ raketab.each_line do |line|
48
+ line.strip!
49
+ unless line =~ /^#/ || line.empty? # ignore comments and blank lines
50
+ sp = line.split
51
+ if SPECIAL_STRINGS.include?(sp.first)
52
+ crontab << sp.shift
53
+ tasks = sp
54
+ else
55
+ crontab << sp[0,5].join(' ')
56
+ tasks = sp[5,sp.size]
57
+ end
58
+ crontab << " cd #{DEPLOY_PATH} && #{RAKE_EXE} --silent RAILS_ENV=#{RAKETAB_RAILS_ENV}"
59
+ tasks.each do |task|
60
+ crontab << " #{task}"
61
+ end
62
+ crontab << "\n"
63
+ end
64
+ end
65
+ crontab << "#{CRONTAB_MARKER_END}\n"
66
+ crontab
67
+ end
68
+
69
+ # install new crontab
70
+ def install(crontab)
71
+ filename = ".crontab#{rand(9999)}"
72
+ File.open(filename, 'w') { |f| f.write crontab }
73
+ `#{CRONTAB_EXE} #{filename}`
74
+ FileUtils.rm filename
75
+ end
76
+
77
+ def raketab(files=RAKETAB_FILES)
78
+ files.map do |file|
79
+ next unless File.exist?(file)
80
+ builder = file =~ /.(\w+)$/ ? "build_raketab_from_#{$1}" : "build_raketab"
81
+ send(builder.to_sym, file)
82
+ end.join("\n")
83
+ end
84
+
85
+ private
86
+ def build_raketab_from_rb(file)
87
+ eval(File.new(file).read).tabs
88
+ end
89
+
90
+ def build_raketab_from_yml(file)
91
+ yml = YAML::load(ERB.new(File.read(file)).result(binding))
92
+ yml.map do |name,tab|
93
+ format = []
94
+ format << (tab['min'] || tab['minute'] || '0')
95
+ format << (tab['hour'] || '0')
96
+ format << (tab['day'] || '*')
97
+ format << (tab['month'] =~ /^\d+$/ ? tab['month'] : Date._parse(tab['month'].to_s)[:mon] || '*')
98
+ format << ((day = tab['weekday'] || tab['wday'] and day =~ /^\d+$/ ? day : Date._parse(day.to_s)[:wday]) || '*')
99
+ format << tab['command']
100
+ format.join(' ')
101
+ end.join("\n")
102
+ end
103
+ alias_method :build_raketab_from_yaml, :build_raketab_from_yml
104
+
105
+ def build_raketab(file)
106
+ ERB.new(File.read(file)).result(binding)
107
+ end
108
+
109
+ def method_missing(method, *args)
110
+ method.to_s =~ /^build_raketab/ ? build_raketab(*args) : super
111
+ end
112
+ end
@@ -0,0 +1,89 @@
1
+ class Enumeration
2
+ include Comparable
3
+
4
+ def initialize(id)
5
+ @unit_id = id % self.class.size
6
+ end
7
+ private_class_method :new
8
+
9
+ def self.for(var)
10
+ units[var.to_s =~ /^\d+$/ ? var.to_i - offset : abbrs.index(var[0,3].downcase.capitalize)]
11
+ end
12
+
13
+ def succ
14
+ self.class.units[(@unit_id + 1) % self.class.size]
15
+ end
16
+
17
+ def between?(a,b)
18
+ true # always in rings, change for non rings
19
+ end
20
+
21
+ def <=>(o)
22
+ @unit_id <=> o.instance_variable_get("@unit_id")
23
+ end
24
+
25
+ def to_s
26
+ self.class.names[@unit_id]
27
+ end
28
+
29
+ def to_i
30
+ @unit_id + self.class.offset
31
+ end
32
+
33
+ def to_abbr
34
+ to_s[0,3]
35
+ end
36
+
37
+ def coerce(o)
38
+ [self.class.for(o % self.class.size), self]
39
+ end
40
+
41
+ def +(o)
42
+ self.class.for((to_i + o.to_i) % self.class.size)
43
+ end
44
+
45
+ def -(o)
46
+ self.class.for((to_i - o.to_i) % self.class.size)
47
+ end
48
+
49
+ def inspect
50
+ "#<#{self.class.name.split("::").last}:#{to_s}>"
51
+ end
52
+
53
+ def self.generate(names, offset=0)
54
+ klass = Class.new(self)
55
+ klass.send(:build_from, names)
56
+ klass.send(:offset=, offset)
57
+ klass.instance_eval { undef generate }
58
+ klass
59
+ end
60
+
61
+ class << self
62
+ include Enumerable
63
+ def each
64
+ units.each { |u| yield u }
65
+ end
66
+
67
+ attr_accessor :offset
68
+ attr_reader :names, :abbrs, :units, :size
69
+ def build_from(names)
70
+ @names = names.dup.freeze
71
+ @abbrs = names.map { |n| n[0,3] }.freeze
72
+
73
+ @size = @names.size
74
+ @units = (0...@size).map { |n| new(n) }.freeze
75
+
76
+ @names.each_with_index do |c,i|
77
+ const = c.upcase
78
+ const_get(const) rescue const_set(const, @units[i])
79
+ const_get(const[0,3]) rescue const_set(const[0,3], @units[i])
80
+ end
81
+ end
82
+ private :build_from
83
+ end
84
+ end
85
+
86
+ def Enumeration(*args)
87
+ Enumeration.generate(*args)
88
+ end
89
+ alias enum Enumeration
@@ -0,0 +1,104 @@
1
+ require "#{File.dirname(__FILE__)}/enumeration"
2
+ require 'date'
3
+
4
+ class Raketab
5
+ Month = enum %w[January February March April May June July August September October November December], 1
6
+ Weekday = enum %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
7
+
8
+ class << self
9
+ def methodize_enum(enum)
10
+ enum.each do |e|
11
+ p = Proc.new { e }
12
+ define_method(e.to_s.downcase, p)
13
+ define_method(e.to_abbr.downcase, p)
14
+ end
15
+ end
16
+ private :methodize_enum
17
+
18
+ def schedule(&block)
19
+ tab = Raketab.new
20
+ tab.instance_eval(&block)
21
+ tab
22
+ end
23
+ end
24
+ methodize_enum(Month)
25
+ methodize_enum(Weekday)
26
+
27
+ def initialize
28
+ @tabs = []
29
+ end
30
+
31
+ def run(command, options={})
32
+ month, wday, mday, hour, min = options[:month] || options[:months] || options[:mon],
33
+ options[:weekday] || options[:weekdays] || options[:wday],
34
+ options[:day] || options[:days] || options[:mday],
35
+ options[:hour] || options[:hours],
36
+ options[:minute] || options[:minutes] || options[:min]
37
+
38
+ # make sure we have ints instead of enums, yo
39
+ month, wday = [[month, Month], [wday, Weekday]].map do |element,type|
40
+ if element.kind_of?(Array) # just arrays for now
41
+ element.each_with_index { |e,i| element[i] = enum_to_i(e,type) }
42
+ else
43
+ enum_to_i(element,type)
44
+ end
45
+ end
46
+
47
+ [:each, :every, :on, :in, :at, :the].each do |type|
48
+ if options[type]
49
+ if(options[type] =~ /:/)
50
+ from = options[type]
51
+ else
52
+ from, ignore, exclusive, to = options[type].to_s.match(/(\w+)(\.\.(\.?)(\w+))?/)[1..4].map { |m| m.gsub(/s$/i, '') if m }
53
+ end
54
+
55
+ parse = Date._parse(from)
56
+ range = to ? Date._parse(to) : {}
57
+
58
+ month ||= get_value(parse, range, exclusive == '.', :mon)
59
+ wday ||= get_value(parse, range, exclusive == '.', :wday)
60
+ mday ||= get_value(parse, range, exclusive == '.', :mday)
61
+ hour ||= get_value(parse, range, exclusive == '.', :hour)
62
+ min ||= get_value(parse, range, exclusive == '.', :min)
63
+ end
64
+ end
65
+
66
+ # deal with any arrays and ranges
67
+ min, hour, mday, wday, month = [min, hour, mday, wday, month].map do |type|
68
+ type.respond_to?(:map) ? type.map.join(',') : type
69
+ end
70
+
71
+ # special cases with hours
72
+ hour ||= options[:at].to_i if options[:at] # :at => "5 o'clock" / "5" / 5
73
+
74
+ # fill missing items
75
+ hour, min = [hour, min].map { |t| t || '0' }
76
+ month, wday, mday = [month, wday, mday].map { |t| t || '*' }
77
+
78
+ # put it together
79
+ @tabs << "#{min} #{hour} #{mday} #{month} #{wday} #{command}"
80
+ end
81
+
82
+ def tabs
83
+ @tabs.join("\n")
84
+ end
85
+
86
+ private
87
+ def get_value(from, to, exclusive, on)
88
+ value = (from[on] and to[on]) ? Range.new(from[on], to[on], exclusive) : from[on]
89
+ if value.is_a?(Range) and value.first > value.last
90
+ reverse = (value.last.to_i+(exclusive ? 0 : 1)..(value.first.to_i-1))
91
+ range = case on
92
+ when :mon then 1..12
93
+ when :wday then 0..6
94
+ when :mday then 1..31
95
+ end
96
+ value = range.map - reverse.map
97
+ end
98
+ value
99
+ end
100
+
101
+ def enum_to_i(element, type)
102
+ element.kind_of?(type) ? element.to_i : element
103
+ end
104
+ end
@@ -0,0 +1,138 @@
1
+ RAILS_ROOT = "foo/bar/baz"
2
+ RAILS_ENV = "test"
3
+ ENV['app_name'] = "craken_test"
4
+ ENV['raketab_rails_env'] = "test"
5
+ ENV['rake_exe'] = '/usr/bin/bundle exec rake'
6
+
7
+ require File.dirname(__FILE__) + "/../lib/craken"
8
+ require 'fileutils'
9
+
10
+ describe Craken do
11
+
12
+ include Craken
13
+
14
+ describe "load_and_strip" do
15
+
16
+ it "should load the user's installed crontab" do
17
+ # figured out how to do this from here:
18
+ # http://jakescruggs.blogspot.com/2007/11/mocking-backticks-and-other-kernel.html
19
+ self.should_receive(:`).with(/crontab -l/).and_return('')
20
+ load_and_strip
21
+ end
22
+
23
+ it "should strip out preinstalled raketab commands associated with the project" do
24
+
25
+ crontab = <<EOS
26
+ ### craken_test test raketab start
27
+ this is a test
28
+ one more line
29
+ ### craken_test test raketab end
30
+ EOS
31
+
32
+ self.should_receive(:`).with(/crontab -l/).and_return(crontab)
33
+ load_and_strip.should be_empty
34
+ end
35
+
36
+ it "should not strip out preinstalled raketab commands not associated with the project" do
37
+
38
+ crontab = <<EOS
39
+ 1 2 3 4 5 blah blah
40
+ ### craken_test test raketab start
41
+ this is a test
42
+ one more line
43
+ ### craken_test test raketab end
44
+ 6 7 8 9 10 foo bar
45
+ EOS
46
+
47
+ self.should_receive(:`).with(/crontab -l/).and_return(crontab)
48
+ load_and_strip.should == "1 2 3 4 5 blah blah\n6 7 8 9 10 foo bar\n"
49
+ end
50
+ end
51
+
52
+ describe "append_tasks" do
53
+ before(:each) do
54
+ @crontab = "1 2 3 4 5 blah blah\n6 7 8 9 10 foo bar\n"
55
+ end
56
+
57
+ it "should add comments to the beginning and end of the rake tasks it adds to crontab" do
58
+ raketab = "0 1 0 0 0 foo:bar"
59
+ cron = append_tasks(@crontab, raketab)
60
+ cron.should match(/### craken_test test raketab start\n0 1 0 0 0 /)
61
+ cron.should match(/### craken_test test raketab end\n$/)
62
+ end
63
+
64
+ it "should ignore comments in the raketab string" do
65
+ raketab = <<EOS
66
+ # comment to ignore
67
+ 0 1 0 0 0 foo:bar
68
+ # another comment to ignore
69
+ EOS
70
+ cron = append_tasks(@crontab, raketab)
71
+ cron.should_not match(/# comment to ignore/)
72
+ cron.should_not match(/# another comment to ignore/)
73
+ end
74
+
75
+ it "should not munge the eight crontab special strings" do
76
+ raketab = <<EOS
77
+ @reboot brush:teeth
78
+ @yearly dont_forget_girlfriends:birthday
79
+ @annually just_damn_remember:it
80
+ @monthly do_some:sport
81
+ @weekly get:stash
82
+ @daily take:shower
83
+ @midnight stop_working_on_os:projects
84
+ @hourly drink:water
85
+ EOS
86
+ cron = append_tasks(@crontab, raketab)
87
+ cron.should match(/@reboot (.*)/)
88
+ cron.should match(/@yearly (.*)/)
89
+ cron.should match(/@annually (.*)/)
90
+ cron.should match(/@monthly (.*)/)
91
+ cron.should match(/@weekly (.*)/)
92
+ cron.should match(/@daily (.*)/)
93
+ cron.should match(/@midnight (.*)/)
94
+ cron.should match(/@hourly (.*)/)
95
+ end
96
+
97
+ it "should not munge the crontab time configuration" do
98
+ raketab = <<EOS
99
+ 0 1 0 0 0 foo:bar
100
+ 1,2,3,4,5,6 0 7,8 4 5 baz:blarg
101
+ EOS
102
+ cron = append_tasks(@crontab, raketab)
103
+ cron.should match(/0 1 0 0 0 [^\d]/)
104
+ cron.should match(/1,2,3,4,5,6 0 7,8 4 5 [^\d]/)
105
+ end
106
+
107
+ it "should add a cd command, rake command and environment variables" do
108
+ raketab = "0 1 0 0 0 foo:bar"
109
+ cron = append_tasks(@crontab, raketab)
110
+ cron.should match(/0 1 0 0 0 cd #{Craken::DEPLOY_PATH} && #{Craken::RAKE_EXE} --silent RAILS_ENV=#{Craken::RAKETAB_RAILS_ENV} foo:bar/)
111
+ end
112
+
113
+ it "should use the rake command given via ENV['rake_exe']" do
114
+ raketab = "0 1 0 0 0 foo:bar"
115
+ cron = append_tasks(@crontab, raketab)
116
+ cron.should include('/usr/bin/bundle exec rake')
117
+ end
118
+
119
+ it "should ignore additional data at the end of the configuration" do
120
+ raketab = "0 1 0 0 0 foo:bar >> /tmp/foobar.log 2>&1"
121
+ cron = append_tasks(@crontab, raketab)
122
+ cron.should match(/0 1 0 0 0 cd #{Craken::DEPLOY_PATH} && #{Craken::RAKE_EXE} --silent RAILS_ENV=#{Craken::RAKETAB_RAILS_ENV} foo:bar >> \/tmp\/foobar.log 2>&1/)
123
+ end
124
+ end
125
+
126
+ describe "install" do
127
+ it "should install crontab by creating a temporary file, running crontab, then deleting the temp file" do
128
+ crontab = "crontab"
129
+ file_handle = mock("file handle")
130
+ file_handle.should_receive(:write).with(crontab)
131
+ File.should_receive(:open).with(/.crontab[0-9]*/, 'w').and_yield(file_handle)
132
+ self.should_receive(:`).with(/crontab .crontab[0-9]*$/)
133
+ FileUtils.should_receive(:rm).with(/.crontab[0-9]*/)
134
+ install(crontab)
135
+ end
136
+ end
137
+
138
+ end
@@ -0,0 +1,136 @@
1
+ describe Enumeration do
2
+ Weekday = enum %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
3
+
4
+ describe 'creation' do
5
+ it "should create an enumeration by Enumeration.generate, Enumeration(), or enum" do
6
+ ABC1 = Enumeration.generate(%w[a b c])
7
+ ABC1.units.should == [ABC1::A, ABC1::B, ABC1::C]
8
+
9
+ ABC2 = Enumeration(%w[a b c])
10
+ ABC2.units.should == [ABC1::A, ABC1::B, ABC1::C]
11
+
12
+ ABC3 = enum %w[a b c]
13
+ ABC3.units.should == [ABC1::A, ABC1::B, ABC1::C]
14
+ end
15
+
16
+ it "should not generate an enumeration itself" do
17
+ ABC4 = enum %w[a b c]
18
+ ABC4.methods.grep(/generate/).should be_empty
19
+ lambda{ ABC4.generate(%w[a b c]) }.should raise_error(NoMethodError)
20
+ end
21
+
22
+ it "should allow an offset from zero to be specified" do
23
+ ABC = enum %w[a b c], 1
24
+ ABC.offset.should == 1
25
+ ABC.size.should == 3
26
+ ABC.units.map { |l| l.to_i }.should == [1,2,3]
27
+ end
28
+ end
29
+
30
+ describe 'properties' do
31
+ it "should generate the right size of elements" do
32
+ Weekday.units.size.should == 7
33
+ Weekday.size.should == Weekday.units.size
34
+ end
35
+
36
+ it "should generate an enumeration based on an collection of names" do
37
+ # full names
38
+ Weekday.names.should == %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
39
+ Weekday.units.should ==
40
+ [Weekday::SUNDAY, Weekday::MONDAY, Weekday::TUESDAY, Weekday::WEDNESDAY, Weekday::THURSDAY, Weekday::FRIDAY, Weekday::SATURDAY]
41
+
42
+ # abbreviationss
43
+ Weekday.abbrs.should == %w[Sun Mon Tue Wed Thu Fri Sat]
44
+ Weekday.units.should ==
45
+ [Weekday::SUN, Weekday::MON, Weekday::TUE, Weekday::WED, Weekday::THU, Weekday::FRI, Weekday::SAT]
46
+ Weekday.units.map { |w| w.to_i }.should == (0..6).map
47
+ end
48
+
49
+ it "should get units based on integer value" do
50
+ (0..6).each do |value|
51
+ Weekday.for(value).should == Weekday.units[value]
52
+ end
53
+
54
+ # and respect offsets
55
+ (1..3).each do |value|
56
+ ABC.for(value).should == ABC.units[value - ABC.offset]
57
+ end
58
+ end
59
+
60
+ it "should be enumerable on the units" do
61
+ Weekday.methods.grep(/^each$/).should == %w[each]
62
+ Weekday.is_a?(Enumerable)
63
+ Weekday.map {|x| x.to_i }.should == Weekday.units.map {|x| x.to_i }
64
+ end
65
+
66
+ it "should not be able to create new units" do
67
+ lambda { Weekday.new(7) }.should raise_error(NoMethodError)
68
+ end
69
+ end
70
+
71
+ describe 'units' do
72
+ it "should be comparable" do
73
+ Weekday::SUN.methods.grep(/^succ$/).should == %w[succ]
74
+ Weekday::SUN.is_a?(Comparable)
75
+ Weekday::SUN.should < Weekday::MON
76
+ Weekday::SUN.should <= Weekday::MON
77
+ Weekday::MON.should > Weekday::SUN
78
+ Weekday::MON.should >= Weekday::SUN
79
+ Weekday::SUN.should == Weekday::SUN
80
+ Weekday::WED.between?(Weekday::MON, Weekday::FRI).should be_true
81
+ Weekday::FRI.between?(Weekday::THU, Weekday::SUN).should be_true # if a ring
82
+
83
+ end
84
+
85
+ # this should be configurable!? is true for now!
86
+ it "should be a ring of values (cross boundries)" do
87
+ Weekday::SAT.succ.should == Weekday::SUN
88
+ Weekday::SUN.succ.should == Weekday::MON
89
+ end
90
+
91
+ it "should work in a range" do
92
+ (Weekday::MON..Weekday::FRI).map.should == Weekday.units[1..5]
93
+ end
94
+
95
+ it "should have to_s be it's name" do
96
+ %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].each do |name|
97
+ Weekday.for(name).to_s.should == name
98
+ end
99
+ end
100
+
101
+ it "should have to_abbr to get it's three letter abbreviation" do
102
+ %w[Sun Mon Tue Wed Thu Fri Sat].each do |abbr|
103
+ Weekday.for(abbr).to_abbr.should == abbr
104
+ end
105
+ end
106
+
107
+ it "should show the enumerable name and unit name in inspect" do
108
+ %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].each do |name|
109
+ Weekday.for(name).inspect.should == "#<Weekday:#{name}>"
110
+ end
111
+ end
112
+
113
+ it "should use integer values in arithmatic (+/-) and return another unit" do
114
+ Weekday.each_with_index do |w,i|
115
+ (Weekday::SUN + i).should == w
116
+ end
117
+ Weekday.map.reverse.each_with_index do |w,i|
118
+ (Weekday::SUN - i - 1).should == w
119
+ end
120
+ # ring / identity
121
+ (Weekday::SUN + 7).should == Weekday::SUN
122
+ (Weekday::SUN - 7).should == Weekday::SUN
123
+ end
124
+
125
+ it "should use integer values in arithmatic (+/-) and return another unit with coersion to weekday on integer" do
126
+ Weekday.each_with_index do |w,i|
127
+ (1 + w).should == Weekday.map[(i+1) % Weekday.size]
128
+ end
129
+ Weekday.each_with_index do |w,i|
130
+ (i - w).should == Weekday::SUN
131
+ end
132
+ (0 + Weekday::MON).should == Weekday::MON
133
+ (2 - Weekday::MON).should == Weekday::MON
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,171 @@
1
+ describe Raketab do
2
+ it "should have defaults (0 for hour, minutes and * for days and months)" do
3
+ Raketab.schedule { run 'test' }.tabs.should == '0 0 * * * test'
4
+ end
5
+
6
+ it "should set all the fields by name" do
7
+ Raketab.schedule { run 'test', :min => 1 }.tabs.should == "1 0 * * * test"
8
+ Raketab.schedule { run 'test', :minute => 1 }.tabs.should == "1 0 * * * test"
9
+ Raketab.schedule { run 'test', :hour => 1 }.tabs.should == "0 1 * * * test"
10
+ Raketab.schedule { run 'test', :day => 1 }.tabs.should == "0 0 1 * * test"
11
+ Raketab.schedule { run 'test', :mday => 1 }.tabs.should == "0 0 1 * * test"
12
+ Raketab.schedule { run 'test', :month => 1 }.tabs.should == "0 0 * 1 * test"
13
+ Raketab.schedule { run 'test', :mon => 1 }.tabs.should == "0 0 * 1 * test"
14
+ Raketab.schedule { run 'test', :wday => 1 }.tabs.should == "0 0 * * 1 test"
15
+ Raketab.schedule { run 'test', :weekday => 1 }.tabs.should == "0 0 * * 1 test"
16
+ end
17
+
18
+ it "should set month and weekday fields by method name" do
19
+ Raketab.schedule { run 'test', :month => january }.tabs.should == "0 0 * 1 * test"
20
+ Raketab.schedule { run 'test', :mon => jan }.tabs.should == "0 0 * 1 * test"
21
+ Raketab.schedule { run 'test', :wday => monday }.tabs.should == "0 0 * * 1 test"
22
+ Raketab.schedule { run 'test', :weekday => mon }.tabs.should == "0 0 * * 1 test"
23
+ end
24
+
25
+ it "should set the fields by comma string" do
26
+ Raketab.schedule { run 'test', :minutes => '1,2,3' }.tabs.should == "1,2,3 0 * * * test"
27
+ Raketab.schedule { run 'test', :hours => '1,2,3' }.tabs.should == "0 1,2,3 * * * test"
28
+ Raketab.schedule { run 'test', :days => '1,2,3' }.tabs.should == "0 0 1,2,3 * * test"
29
+ Raketab.schedule { run 'test', :months => '1,2,3' }.tabs.should == "0 0 * 1,2,3 * test"
30
+ Raketab.schedule { run 'test', :weekdays => '1,2,3' }.tabs.should == "0 0 * * 1,2,3 test"
31
+ end
32
+
33
+ it "should set the fields by array populated by method names" do
34
+ Raketab.schedule { run 'test', :months => [jan,feb,mar] }.tabs.should == "0 0 * 1,2,3 * test"
35
+ Raketab.schedule { run 'test', :weekdays => [mon,tue,wed] }.tabs.should == "0 0 * * 1,2,3 test"
36
+ end
37
+
38
+ it "should set the fields by array" do
39
+ Raketab.schedule { run 'test', :minutes => [1,2,3] }.tabs.should == "1,2,3 0 * * * test"
40
+ Raketab.schedule { run 'test', :hours => [1,2,3] }.tabs.should == "0 1,2,3 * * * test"
41
+ Raketab.schedule { run 'test', :days => [1,2,3] }.tabs.should == "0 0 1,2,3 * * test"
42
+ Raketab.schedule { run 'test', :months => [1,2,3] }.tabs.should == "0 0 * 1,2,3 * test"
43
+ Raketab.schedule { run 'test', :weekdays => [1,2,3] }.tabs.should == "0 0 * * 1,2,3 test"
44
+ end
45
+
46
+ it "should set the fields by inclusive ranges" do
47
+ Raketab.schedule { run 'test', :minutes => 1..3 }.tabs.should == "1,2,3 0 * * * test"
48
+ Raketab.schedule { run 'test', :hours => 1..3 }.tabs.should == "0 1,2,3 * * * test"
49
+ Raketab.schedule { run 'test', :days => 1..3 }.tabs.should == "0 0 1,2,3 * * test"
50
+ Raketab.schedule { run 'test', :months => 1..3 }.tabs.should == "0 0 * 1,2,3 * test"
51
+ Raketab.schedule { run 'test', :weekdays => 1..3 }.tabs.should == "0 0 * * 1,2,3 test"
52
+ end
53
+
54
+ it "should set the fields by exclusive ranges" do
55
+ Raketab.schedule { run 'test', :minutes => 1...4 }.tabs.should == "1,2,3 0 * * * test"
56
+ Raketab.schedule { run 'test', :hours => 1...4 }.tabs.should == "0 1,2,3 * * * test"
57
+ Raketab.schedule { run 'test', :days => 1...4 }.tabs.should == "0 0 1,2,3 * * test"
58
+ Raketab.schedule { run 'test', :months => 1...4 }.tabs.should == "0 0 * 1,2,3 * test"
59
+ Raketab.schedule { run 'test', :weekdays => 1...4 }.tabs.should == "0 0 * * 1,2,3 test"
60
+ end
61
+
62
+ it "should set weekday with symbol or string of the day name or abbreviation" do
63
+ Raketab.schedule { run 'test', :on => :thursday }.tabs.should == '0 0 * * 4 test'
64
+ Raketab.schedule { run 'test', :on => 'Thursday' }.tabs.should == '0 0 * * 4 test'
65
+ Raketab.schedule { run 'test', :on => :thu }.tabs.should == '0 0 * * 4 test'
66
+ Raketab.schedule { run 'test', :on => 'Thurs' }.tabs.should == '0 0 * * 4 test'
67
+ end
68
+
69
+
70
+ it "should set month with symbol or string of the month name or abbreviation" do
71
+ Raketab.schedule { run 'test', :on => :september }.tabs.should == '0 0 * 9 * test'
72
+ Raketab.schedule { run 'test', :on => 'September' }.tabs.should == '0 0 * 9 * test'
73
+ Raketab.schedule { run 'test', :on => :sep }.tabs.should == '0 0 * 9 * test'
74
+ Raketab.schedule { run 'test', :on => 'Sep' }.tabs.should == '0 0 * 9 * test'
75
+ end
76
+
77
+ it "should set month with any syntactic sugar" do
78
+ Raketab.schedule { run 'test', :every => :september }.tabs.should == '0 0 * 9 * test'
79
+ Raketab.schedule { run 'test', :each => :september }.tabs.should == '0 0 * 9 * test'
80
+ Raketab.schedule { run 'test', :on => :september }.tabs.should == '0 0 * 9 * test'
81
+ Raketab.schedule { run 'test', :in => :september }.tabs.should == '0 0 * 9 * test'
82
+ end
83
+
84
+ it "should set day with any syntactic sugar" do
85
+ Raketab.schedule { run 'test', :every => '1st' }.tabs.should == '0 0 1 * * test'
86
+ Raketab.schedule { run 'test', :each => '2nd' }.tabs.should == '0 0 2 * * test'
87
+ Raketab.schedule { run 'test', :the => '3rd' }.tabs.should == '0 0 3 * * test'
88
+ end
89
+
90
+ it "should set the time of day, with AM/PM or military time" do
91
+ Raketab.schedule { run 'no meridiem', :at => '12:30' }.tabs.should == '30 12 * * * no meridiem'
92
+ Raketab.schedule { run 'am meridiem', :at => '12:30AM' }.tabs.should == '30 0 * * * am meridiem'
93
+ Raketab.schedule { run 'pm meridiem', :at => '12:30PM' }.tabs.should == '30 12 * * * pm meridiem'
94
+ Raketab.schedule { run 'military', :at => '23:30' }.tabs.should == '30 23 * * * military'
95
+ end
96
+
97
+ it "should set all the fields if they are all provided" do
98
+ Raketab.schedule do |t|
99
+ t.run 'test', :every => :sep, :the => '1st', :on => :thursdays, :at => "12:30"
100
+ end.tabs.should == "30 12 1 9 4 test"
101
+ end
102
+
103
+ it "should set the range of months" do
104
+ Raketab.schedule { run 'inclusive full', :in => 'September..November' }.tabs.should == '0 0 * 9,10,11 * inclusive full'
105
+ Raketab.schedule { run 'exclusive full', :in => 'September...December' }.tabs.should == '0 0 * 9,10,11 * exclusive full'
106
+ Raketab.schedule { run 'inclusive abbr', :in => 'Sep..Nov' }.tabs.should == '0 0 * 9,10,11 * inclusive abbr'
107
+ Raketab.schedule { run 'exclusive abbr', :in => 'Sep...Dec' }.tabs.should == '0 0 * 9,10,11 * exclusive abbr'
108
+ end
109
+
110
+ it "should set the range of months across year boundry" do
111
+ Raketab.schedule { run 'inclusive full reverse', :in => 'November..January' }.tabs.should == '0 0 * 1,11,12 * inclusive full reverse'
112
+ Raketab.schedule { run 'exclusive full reverse', :in => 'November...February' }.tabs.should == '0 0 * 1,11,12 * exclusive full reverse'
113
+ Raketab.schedule { run 'inclusive abbr reverse', :in => 'Nov..Jan' }.tabs.should == '0 0 * 1,11,12 * inclusive abbr reverse'
114
+ Raketab.schedule { run 'exclusive abbr reverse', :in => 'Nov...Feb' }.tabs.should == '0 0 * 1,11,12 * exclusive abbr reverse'
115
+ end
116
+
117
+ it "should set the range of days" do
118
+ Raketab.schedule { run 'inclusive full', :on => 'Monday..Thursday' }.tabs.should == '0 0 * * 1,2,3,4 inclusive full'
119
+ Raketab.schedule { run 'exclusive full', :on => 'Monday...Friday' }.tabs.should == '0 0 * * 1,2,3,4 exclusive full'
120
+ Raketab.schedule { run 'inclusive abbr', :on => 'Mon..Thur' }.tabs.should == '0 0 * * 1,2,3,4 inclusive abbr'
121
+ Raketab.schedule { run 'exclusive abbr', :on => 'Mon...Fri' }.tabs.should == '0 0 * * 1,2,3,4 exclusive abbr'
122
+ end
123
+
124
+ it "should set the range of days across week boundry" do
125
+ Raketab.schedule { run 'inclusive full', :on => 'Thursday..Monday' }.tabs.should == '0 0 * * 0,1,4,5,6 inclusive full'
126
+ Raketab.schedule { run 'exclusive full', :on => 'Thursday...Monday' }.tabs.should == '0 0 * * 0,4,5,6 exclusive full'
127
+ Raketab.schedule { run 'inclusive abbr', :on => 'Thu..Mon' }.tabs.should == '0 0 * * 0,1,4,5,6 inclusive abbr'
128
+ Raketab.schedule { run 'exclusive abbr', :on => 'Thu...Mon' }.tabs.should == '0 0 * * 0,4,5,6 exclusive abbr'
129
+ end
130
+
131
+ it "should set the range of months using month name methods" do
132
+ Raketab.schedule { run 'inclusive full', :in => september..november }.tabs.should == '0 0 * 9,10,11 * inclusive full'
133
+ Raketab.schedule { run 'exclusive full', :in => september...december }.tabs.should == '0 0 * 9,10,11 * exclusive full'
134
+ Raketab.schedule { run 'inclusive abbr', :in => sep..nov }.tabs.should == '0 0 * 9,10,11 * inclusive abbr'
135
+ Raketab.schedule { run 'exclusive abbr', :in => sep...dec }.tabs.should == '0 0 * 9,10,11 * exclusive abbr'
136
+ end
137
+
138
+ it "should set the range of months across year boundry using month name methods" do
139
+ Raketab.schedule { run 'inclusive full reverse', :in => november..january }.tabs.should == '0 0 * 1,11,12 * inclusive full reverse'
140
+ Raketab.schedule { run 'exclusive full reverse', :in => november...february }.tabs.should == '0 0 * 1,11,12 * exclusive full reverse'
141
+ Raketab.schedule { run 'inclusive abbr reverse', :in => nov..jan }.tabs.should == '0 0 * 1,11,12 * inclusive abbr reverse'
142
+ Raketab.schedule { run 'exclusive abbr reverse', :in => nov...feb }.tabs.should == '0 0 * 1,11,12 * exclusive abbr reverse'
143
+ end
144
+
145
+ it "should set the range of days using weekday name methods" do
146
+ Raketab.schedule { run 'inclusive full', :on => monday..thursday }.tabs.should == '0 0 * * 1,2,3,4 inclusive full'
147
+ Raketab.schedule { run 'exclusive full', :on => monday...friday }.tabs.should == '0 0 * * 1,2,3,4 exclusive full'
148
+ Raketab.schedule { run 'inclusive abbr', :on => mon..thu }.tabs.should == '0 0 * * 1,2,3,4 inclusive abbr'
149
+ Raketab.schedule { run 'exclusive abbr', :on => mon...fri }.tabs.should == '0 0 * * 1,2,3,4 exclusive abbr'
150
+ end
151
+
152
+ it "should set the range of days across week boundry using weekday name methods" do
153
+ Raketab.schedule { run 'inclusive full', :on => thursday..monday }.tabs.should == '0 0 * * 0,1,4,5,6 inclusive full'
154
+ Raketab.schedule { run 'exclusive full', :on => thursday...monday }.tabs.should == '0 0 * * 0,4,5,6 exclusive full'
155
+ Raketab.schedule { run 'inclusive abbr', :on => thu..mon }.tabs.should == '0 0 * * 0,1,4,5,6 inclusive abbr'
156
+ Raketab.schedule { run 'exclusive abbr', :on => thu...mon }.tabs.should == '0 0 * * 0,4,5,6 exclusive abbr'
157
+ end
158
+
159
+ it "should set the time with just a number on at" do
160
+ Raketab.schedule { run 'test', :at => "5 o'clock" }.tabs.should == '0 5 * * * test'
161
+ Raketab.schedule { run 'test', :at => "5" }.tabs.should == '0 5 * * * test'
162
+ Raketab.schedule { run 'test', :at => 5 }.tabs.should == '0 5 * * * test'
163
+ end
164
+
165
+ it "should handle multiple cron jobs" do
166
+ Raketab.schedule do
167
+ run '1st', :at => "5 o'clock"
168
+ run '2nd', :on => :thursday
169
+ end.tabs.should == "0 5 * * * 1st\n0 0 * * 4 2nd"
170
+ end
171
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: did_craken
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Doug McInnes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-14 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Craken is a rails plugin for managing and installing rake-centric crontab files over Capistrano.
23
+ email:
24
+ - didier@nocoffee.fr
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.rdoc
31
+ files:
32
+ - lib/craken.rb
33
+ - lib/enumeration.rb
34
+ - lib/raketab.rb
35
+ - README.rdoc
36
+ - spec/craken_spec.rb
37
+ - spec/enumeration_spec.rb
38
+ - spec/raketab_spec.rb
39
+ has_rdoc: true
40
+ homepage: https://github.com/latimes/craken
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.4.2
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A Rails plugin for managing and installing rake-centric crontab files.
73
+ test_files:
74
+ - spec/craken_spec.rb
75
+ - spec/enumeration_spec.rb
76
+ - spec/raketab_spec.rb