summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xlib/puppet/type/cron.rb343
-rwxr-xr-xtest/types/cron.rb105
2 files changed, 294 insertions, 154 deletions
diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb
index 88ea83b4d..e3bac17fc 100755
--- a/lib/puppet/type/cron.rb
+++ b/lib/puppet/type/cron.rb
@@ -116,21 +116,25 @@ module Puppet
# out which event to return. Finally, call @parent.sync to write the
# cron tab.
def sync
- @parent.store
-
event = nil
if @is == :notfound
#@is = @should
event = :cron_created
+ # We're the first state, so if we're creating the job
+ # then sync all of the other states
+ @parent.eachstate { |state|
+ next if state == self
+ state.sync(true)
+ }
+
+ @is = self.should
elsif self.should == :notfound
@parent.remove(true)
event = :cron_deleted
- elsif self.should == @is
- self.err "Uh, they're both %s" % self.should
+ elsif self.insync?
return nil
else
- #@is = @should
- self.err "@is is %s" % @is
+ @is = self.should
event = :cron_changed
end
@@ -139,6 +143,170 @@ module Puppet
return event
end
end
+
+ # A base class for all of the Cron parameters, since they all have
+ # similar argument checking going on.
+ class CronParam < Puppet::State
+ class << self
+ attr_reader :checking
+ end
+
+ def weekdays
+ %w{sunday monday tuesday wednesday thursday friday saturday}
+ end
+
+ def months
+ %w{january february march april may june july
+ august september october november december}
+ end
+
+ # Normally this would retrieve the current value, but our state is not
+ # actually capable of doing so. The Cron class does the actual tab
+ # retrieval, so all this method does is default to :notfound for @is.
+ def retrieve
+ unless defined? @is and ! @is.nil?
+ @is = :notfound
+ end
+ end
+
+ # Determine whether the cron job should be destroyed, and figure
+ # out which event to return. Finally, call @parent.sync to write the
+ # cron tab.
+ def sync(nostore = false)
+ event = nil
+ if @is == :notfound
+ @is = self.should
+ event = :cron_created
+ elsif self.should == :notfound
+ @parent.remove(true)
+ event = :cron_deleted
+ elsif self.insync?
+ return nil
+ else
+ @is = self.should
+ event = :cron_changed
+ end
+
+ unless nostore
+ @parent.store
+ end
+
+ return event
+ end
+
+ # A method used to do parameter input handling. Converts integers
+ # in string form to actual integers, and returns the value if it's
+ # an integer or false if it's just a normal string.
+ def numfix(num)
+ if num =~ /^\d+$/
+ return num.to_i
+ elsif num.is_a?(Integer)
+ return num
+ else
+ return false
+ end
+ end
+
+ # Verify that a number is within the specified limits. Return the
+ # number if it is, or false if it is not.
+ def limitcheck(num, lower, upper)
+ if num >= lower and num <= upper
+ return num
+ else
+ return false
+ end
+ end
+
+ # Verify that a value falls within the specified array. Does case
+ # insensitive matching, and supports matching either the entire word
+ # or the first three letters of the word.
+ def alphacheck(value, ary)
+ tmp = value.downcase
+ if tmp.length == 3
+ ary.each_with_index { |name, index|
+ if name =~ /#{tmp}/i
+ return index
+ end
+ }
+ else
+ if ary.include?(tmp)
+ return ary.index(tmp)
+ end
+ end
+
+ return false
+ end
+
+ def should_to_s
+ if @should.empty?
+ return "*"
+ else
+ return @should.join(",")
+ end
+ end
+
+ # The method that does all of the actual parameter value
+ # checking; called by all of the +param<name>=+ methods.
+ # Requires the value, type, and bounds, and optionally supports
+ # a boolean of whether to do alpha checking, and if so requires
+ # the ary against which to do the checking.
+ def validate(value, lower, upper, arymethod = nil)
+ retval = nil
+ if num = numfix(value)
+ retval = limitcheck(num, lower, upper)
+ elsif arymethod
+ retval = alphacheck(value, send(arymethod))
+ end
+
+ if retval
+ return retval
+ else
+ raise Puppet::Error, "%s is not a valid %s" %
+ [value, self.class.name]
+ end
+ end
+
+ def shouldprocess(value)
+ val = validate(value, *self.class.checking)
+ return val.to_s
+ end
+ end
+
+ class CronMinute < CronParam
+ @name = :minute
+ @checking = [0, 59]
+ @doc = "The minute at which to run the cron job.
+ Optional; if specified, must be between 0 and 59, inclusive."
+ end
+
+ class CronHour < CronParam
+ @name = :hour
+ @checking = [0, 23]
+ @doc = "The hour at which to run the cron job. Optional;
+ if specified, must be between 0 and 23, inclusive."
+ end
+
+ class CronWeekday < CronParam
+ @name = :weekday
+ @checking = [0, 6, :weekdays]
+ @doc = "The weekday on which to run the command.
+ Optional; if specified, must be between 0 and 6, inclusive, with
+ 0 being Sunday, or must be the name of the day (e.g., Tuesday)."
+ end
+
+ class CronMonth < CronParam
+ @name = :month
+ @checking = [1, 12, :months]
+ @doc = "The month of the year. Optional; if specified
+ must be between 1 and 12 or the month name (e.g., December)."
+ end
+
+ class CronMonthDay < CronParam
+ @name = :monthday
+ @checking = [1, 31]
+ @doc = "The day of the month on which to run the
+ command. Optional; if specified, must be between 1 and 31."
+ end
end
class Type
@@ -148,17 +316,16 @@ module Puppet
# and is used to manage the job.
class Cron < Type
@states = [
- Puppet::State::CronCommand
+ Puppet::State::CronCommand,
+ Puppet::State::CronMinute,
+ Puppet::State::CronHour,
+ Puppet::State::CronWeekday,
+ Puppet::State::CronMonth,
+ Puppet::State::CronMonthDay
]
-
@parameters = [
- :name,
:user,
- :minute,
- :hour,
- :weekday,
- :month,
- :monthday
+ :name
]
@paramdoc[:name] = "The symbolic name of the cron job. This name
@@ -166,17 +333,6 @@ module Puppet
@paramdoc[:user] = "The user to run the command as. This user must
be allowed to run cron jobs, which is not currently checked by
Puppet."
- @paramdoc[:minute] = "The minute at which to run the cron job.
- Optional; if specified, must be between 0 and 59, inclusive."
- @paramdoc[:hour] = "The hour at which to run the cron job. Optional;
- if specified, must be between 0 and 23, inclusive."
- @paramdoc[:weekday] = "The weekday on which to run the command.
- Optional; if specified, must be between 0 and 6, inclusive, with
- 0 being Sunday, or must be the name of the day (e.g., Tuesday)."
- @paramdoc[:month] = "The month of the year. Optional; if specified
- must be between 1 and 12 or the month name (e.g., December)."
- @paramdoc[:monthday] = "The day of the month on which to run the
- command. Optional; if specified, must be between 1 and 31."
@doc = "Installs and manages cron jobs. All fields except the command
and the user are optional, although specifying no periodic
@@ -196,11 +352,6 @@ module Puppet
@instances = {}
- @@weekdays = %w{sunday monday tuesday wednesday thursday friday saturday}
-
- @@months = %w{january february march april may june july
- august september october november december}
-
case Facter["operatingsystem"].value
when "SunOS":
@crontype = Puppet::CronType::SunOS
@@ -289,17 +440,21 @@ module Puppet
# add other comments to the list as they are
@instances[user] << line
else
- ary = line.split(" ")
- fields().each { |param|
- value = ary.shift
- unless value == "*"
- hash[param] = value
- end
- }
-
- if ary.length > 0
- hash[:command] += " " + ary.join(" ")
+ if match = /^(\S+) (\S+) (\S+) (\S+) (\S+) (.+)$/.match(line)
+ fields().zip(match.captures).each { |param, value|
+ unless value == "*"
+ unless param == :command
+ if value =~ /,/
+ value = value.split(",")
+ end
+ end
+ hash[param] = value
+ end
+ }
+ else
+ raise Puppet::Error, "Could not match '%s'" % line
end
+
cron = nil
unless name
Puppet.info "Autogenerating name for %s" % hash[:command]
@@ -348,7 +503,6 @@ module Puppet
# method. Returns nil if there was no cron job; else, returns the
# number of cron instances found.
def self.retrieve(user)
- #%x{crontab -u #{user} -l 2>/dev/null}.split("\n").each { |line|
text = @crontype.read(user)
if $? != 0
# there is no cron file
@@ -398,99 +552,6 @@ module Puppet
end
end
- # A method used to do parameter input handling. Converts integers
- # in string form to actual integers, and returns the value if it's
- # an integer or false if it's just a normal string.
- def numfix(num)
- if num =~ /^\d+$/
- return num.to_i
- elsif num.is_a?(Integer)
- return num
- else
- return false
- end
- end
-
- # Verify that a number is within the specified limits. Return the
- # number if it is, or false if it is not.
- def limitcheck(num, lower, upper)
- if num >= lower and num <= upper
- return num
- else
- return false
- end
- end
-
- # Verify that a value falls within the specified array. Does case
- # insensitive matching, and supports matching either the entire word
- # or the first three letters of the word.
- def alphacheck(value, ary)
- tmp = value.downcase
- if tmp.length == 3
- ary.each_with_index { |name, index|
- if name =~ /#{tmp}/i
- return index
- end
- }
- else
- if ary.include?(tmp)
- return ary.index(tmp)
- end
- end
-
- return false
- end
-
- # The method that does all of the actual parameter value checking; called
- # by all of the +param<name>=+ methods. Requires the value, type, and
- # bounds, and optionally supports a boolean of whether to do alpha
- # checking, and if so requires the ary against which to do the checking.
- def parameter(values, type, lower, upper, alpha = nil, ary = nil)
- unless values.is_a?(Array)
- if values =~ /,/
- values = values.split(/,/)
- else
- values = [values]
- end
- end
-
- @parameters[type] = values.collect { |value|
- retval = nil
- if num = numfix(value)
- retval = limitcheck(num, lower, upper)
- elsif alpha
- retval = alphacheck(value, ary)
- end
-
- if retval
- @parameters[type] = retval
- else
- raise Puppet::Error, "%s is not a valid %s" %
- [value, type]
- end
- }
- end
-
- def paramminute=(value)
- parameter(value, :minute, 0, 59)
- end
-
- def paramhour=(value)
- parameter(value, :hour, 0, 23)
- end
-
- def paramweekday=(value)
- parameter(value, :weekday, 0, 6, true, @@weekdays)
- end
-
- def parammonth=(value)
- parameter(value, :month, 1, 12, true, @@months)
- end
-
- def parammonthday=(value)
- parameter(value, :monthday, 1, 31)
- end
-
def paramuser=(user)
require 'etc'
@@ -511,7 +572,7 @@ module Puppet
end
self.class.retrieve(@parameters[:user])
- @states[:command].retrieve
+ self.eachstate { |st| st.retrieve }
end
# Write the entire user's cron tab out.
@@ -523,17 +584,17 @@ module Puppet
# as a comment above the cron job, in the form '# Puppet Name: <name>'.
def to_cron
hash = {:command => @states[:command].should || @states[:command].is }
+
+ # Collect all of the values that we have
self.class.fields().reject { |f| f == :command }.each { |param|
- hash[param] = @parameters[param] || "*"
+ if @states.include?(param)
+ hash[param] = @states[param].should_to_s
+ end
}
return "# Puppet Name: %s\n" % self.name +
self.class.fields.collect { |f|
- if hash[f].is_a?(Array)
- hash[f].join(",")
- else
- hash[f]
- end
+ hash[f] || "*"
}.join(" ")
end
end
diff --git a/test/types/cron.rb b/test/types/cron.rb
index bcd5be93c..1a60eecd3 100755
--- a/test/types/cron.rb
+++ b/test/types/cron.rb
@@ -12,7 +12,15 @@ require 'puppet/type/cron'
require 'test/unit'
require 'facter'
-class TestExec < Test::Unit::TestCase
+
+# Here we just want to unit-test our cron type, to verify that
+class TestCronType < Test::Unit::TestCase
+ include TestPuppet
+
+
+end
+
+class TestCron < Test::Unit::TestCase
include TestPuppet
def setup
super
@@ -28,6 +36,38 @@ class TestExec < Test::Unit::TestCase
end
# god i'm lazy
@crontype = Puppet::Type::Cron
+
+ # Here we just create a fake cron type that answers to all of the methods
+ # but does not modify our actual system.
+ unless defined? @fakecrontype
+ @fakecrontype = Class.new {
+ @tabs = Hash.new("")
+ def self.clear
+ @tabs = Hash.new("")
+ end
+
+ def self.read(user)
+ @tabs[user]
+ end
+
+ def self.write(user, text)
+ @tabs[user] = text
+ end
+
+ def self.remove(user)
+ @tabs.delete(user)
+ end
+ }
+
+ @oldcrontype = @crontype.crontype
+ @crontype.crontype = @fakecrontype
+ end
+ end
+
+ def teardown
+ @crontype.crontype = @oldcrontype
+ @fakecrontype.clear
+ super
end
# Back up the user's existing cron tab if they have one.
@@ -107,7 +147,7 @@ class TestExec < Test::Unit::TestCase
assert_nothing_raised {
cron = @crontype.create(
:name => name,
- :command => "date",
+ :command => "date > /dev/null",
:user => @me
)
}
@@ -116,9 +156,54 @@ class TestExec < Test::Unit::TestCase
assert_nothing_raised {
str = cron.to_cron
}
- assert_equal(str, "# Puppet Name: #{name}\n* * * * * date",
+
+ assert_equal(str, "# Puppet Name: #{name}\n* * * * * date > /dev/null",
"Cron did not generate correctly")
end
+
+ # Test that changing any field results in the cron tab being rewritten.
+ # it directly
+ def test_any_field_changes
+ cron = nil
+ # make the cron
+ name = "yaytest"
+ assert_nothing_raised {
+ cron = @crontype.create(
+ :name => name,
+ :command => "date > /dev/null",
+ :month => "May",
+ :user => @me
+ )
+ }
+ assert_nothing_raised {
+ cron.sync
+ }
+ assert_nothing_raised {
+ cron[:month] = "June"
+ }
+ comp = newcomp(cron)
+
+ assert_events(comp, [:cron_changed], "did not change cron job")
+ end
+
+ # Test that a cron job with spaces at the end doesn't get rewritten
+ def test_trailingspaces
+ cron = nil
+ # make the cron
+ name = "yaytest"
+ assert_nothing_raised {
+ cron = @crontype.create(
+ :name => name,
+ :command => "date > /dev/null ",
+ :month => "May",
+ :user => @me
+ )
+ }
+ comp = newcomp(cron)
+
+ assert_events(comp, [:cron_created], "did not create cron job")
+ assert_events(comp, [], "cron job got rewritten")
+ end
# Test that comments are correctly retained
def test_retain_comments
@@ -162,16 +247,13 @@ class TestExec < Test::Unit::TestCase
# Test adding a cron when there is currently no file.
def test_mkcronwithnotab
- cronback
Puppet::Type::Cron.crontype.remove(@me)
cron = mkcron("testwithnotab")
cyclecron(cron)
- cronrestore
end
def test_mkcronwithtab
- cronback
Puppet::Type::Cron.crontype.remove(@me)
Puppet::Type::Cron.crontype.write(@me,
"1 1 1 1 * date > %s/crontesting\n" % testdir()
@@ -179,11 +261,9 @@ class TestExec < Test::Unit::TestCase
cron = mkcron("testwithtab")
cyclecron(cron)
- cronrestore
end
def test_makeandretrievecron
- cronback
Puppet::Type::Cron.crontype.remove(@me)
name = "storeandretrieve"
@@ -198,20 +278,19 @@ class TestExec < Test::Unit::TestCase
assert(cron = Puppet::Type::Cron[name], "Could not retrieve named cron")
assert_instance_of(Puppet::Type::Cron, cron)
- cronrestore
end
# Do input validation testing on all of the parameters.
def test_arguments
values = {
:monthday => {
- :valid => [ 1, 13, "1,30" ],
+ :valid => [ 1, 13, "1" ],
:invalid => [ -1, 0, 32 ]
},
:weekday => {
- :valid => [ 0, 3, 6, "1,2", "tue", "wed",
+ :valid => [ 0, 3, 6, "1", "tue", "wed",
"Wed", "MOnday", "SaTurday" ],
- :invalid => [ -1, 7, "1, 3", "tues", "teusday", "thurs" ]
+ :invalid => [ -1, 7, "13", "tues", "teusday", "thurs" ]
},
:hour => {
:valid => [ 0, 21, 23 ],
@@ -238,7 +317,7 @@ class TestExec < Test::Unit::TestCase
}
if value.is_a?(Integer)
- assert_equal([value], cron[param],
+ assert_equal(value.to_s, cron.should(param),
"Cron value was not set correctly")
end
when :invalid: