diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb index a3d80842e..3e8f2bc9b 100644 --- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb @@ -1,560 +1,564 @@ require 'puppet/parameter' if Puppet.features.microsoft_windows? require 'win32/taskscheduler' require 'puppet/util/adsi' end Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do - desc 'This uses the win32-taskscheduler gem to provide support for - managing scheduled tasks on Windows.' + desc %q{This provider uses the win32-taskscheduler gem to manage scheduled + tasks on Windows. + + Puppet requires version 0.2.1 or later of the win32-taskscheduler gem; + previous versions can cause "Could not evaluate: The operation completed + successfully" errors.} defaultfor :operatingsystem => :windows confine :operatingsystem => :windows def self.instances Win32::TaskScheduler.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') new( :provider => :win32_taskscheduler, :name => job_title ) end end def exists? Win32::TaskScheduler.new.exists? resource[:name] end def task return @task if @task @task ||= Win32::TaskScheduler.new @task.activate(resource[:name] + '.job') if exists? @task end def clear_task @task = nil @triggers = nil end def enabled task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false end def command task.application_name end def arguments task.parameters end def working_dir task.working_directory end def user account = task.account_information return 'system' if account == '' account end def trigger return @triggers if @triggers @triggers = [] task.trigger_count.times do |i| trigger = begin task.trigger(i) rescue Win32::TaskScheduler::Error => e # Win32::TaskScheduler can't handle all of the # trigger types Windows uses, so we need to skip the # unhandled types to prevent "puppet resource" from # blowing up. nil end next unless trigger and scheduler_trigger_types.include?(trigger['trigger_type']) puppet_trigger = {} case trigger['trigger_type'] when Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY puppet_trigger['schedule'] = 'daily' puppet_trigger['every'] = trigger['type']['days_interval'].to_s when Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY puppet_trigger['schedule'] = 'weekly' puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s puppet_trigger['on'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['on'] = days_from_bitfield(trigger['type']['days']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['which_occurrence'] = occurrence_constant_to_name(trigger['type']['weeks']) puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE puppet_trigger['schedule'] = 'once' end puppet_trigger['start_date'] = self.class.normalized_date("#{trigger['start_year']}-#{trigger['start_month']}-#{trigger['start_day']}") puppet_trigger['start_time'] = self.class.normalized_time("#{trigger['start_hour']}:#{trigger['start_minute']}") puppet_trigger['enabled'] = trigger['flags'] & Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED == 0 puppet_trigger['index'] = i @triggers << puppet_trigger end @triggers = @triggers[0] if @triggers.length == 1 @triggers end def user_insync?(current, should) return false unless current # Win32::TaskScheduler can return the 'SYSTEM' account as the # empty string. current = 'system' if current == '' # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of the account name. Puppet::Util::ADSI.sid_for_account(current) == Puppet::Util::ADSI.sid_for_account(should[0]) end def trigger_insync?(current, should) should = [should] unless should.is_a?(Array) current = [current] unless current.is_a?(Array) return false unless current.length == should.length current_in_sync = current.all? do |c| should.any? {|s| triggers_same?(c, s)} end should_in_sync = should.all? do |s| current.any? {|c| triggers_same?(c,s)} end current_in_sync && should_in_sync end def command=(value) task.application_name = value end def arguments=(value) task.parameters = value end def working_dir=(value) task.working_directory = value end def enabled=(value) if value == :true task.flags = task.flags & ~Win32::TaskScheduler::DISABLED else task.flags = task.flags | Win32::TaskScheduler::DISABLED end end def trigger=(value) desired_triggers = value.is_a?(Array) ? value : [value] current_triggers = trigger.is_a?(Array) ? trigger : [trigger] extra_triggers = [] desired_to_search = desired_triggers.dup current_triggers.each do |current| if found = desired_to_search.find {|desired| triggers_same?(current, desired)} desired_to_search.delete(found) else extra_triggers << current['index'] end end needed_triggers = [] current_to_search = current_triggers.dup desired_triggers.each do |desired| if found = current_to_search.find {|current| triggers_same?(current, desired)} current_to_search.delete(found) else needed_triggers << desired end end extra_triggers.reverse_each do |index| task.delete_trigger(index) end needed_triggers.each do |trigger_hash| # Even though this is an assignment, the API for # Win32::TaskScheduler ends up appending this trigger to the # list of triggers for the task, while #add_trigger is only able # to replace existing triggers. *shrug* task.trigger = translate_hash_to_trigger(trigger_hash) end end def user=(value) self.fail("Invalid user: #{value}") unless Puppet::Util::ADSI.sid_for_account(value) if value.to_s.downcase != 'system' task.set_account_information(value, resource[:password]) else # Win32::TaskScheduler treats a nil/empty username & password as # requesting the SYSTEM account. task.set_account_information(nil, nil) end end def create clear_task @task = Win32::TaskScheduler.new(resource[:name], dummy_time_trigger) self.command = resource[:command] [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| send("#{prop}=", resource[prop]) if resource[prop] end end def destroy Win32::TaskScheduler.new.delete(resource[:name] + '.job') end def flush unless resource[:ensure] == :absent self.fail('Parameter command is required.') unless resource[:command] task.save end end def triggers_same?(current_trigger, desired_trigger) return false unless current_trigger['schedule'] == desired_trigger['schedule'] return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] desired = desired_trigger.dup desired['every'] ||= current_trigger['every'] if current_trigger.has_key?('every') desired['months'] ||= current_trigger['months'] if current_trigger.has_key?('months') desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') desired['day_of_week'] ||= current_trigger['day_of_week'] if current_trigger.has_key?('day_of_week') translate_hash_to_trigger(current_trigger) == translate_hash_to_trigger(desired) end def self.normalized_date(date_string) date = Date.parse("#{date_string}") "#{date.year}-#{date.month}-#{date.day}" end def self.normalized_time(time_string) Time.parse("#{time_string}").strftime('%H:%M') end def dummy_time_trigger now = Time.now { 'flags' => 0, 'random_minutes_interval' => 0, 'end_day' => 0, "end_year" => 0, "trigger_type" => 0, "minutes_interval" => 0, "end_month" => 0, "minutes_duration" => 0, 'start_year' => now.year, 'start_month' => now.month, 'start_day' => now.day, 'start_hour' => now.hour, 'start_minute' => now.min, 'trigger_type' => Win32::TaskScheduler::ONCE, } end def translate_hash_to_trigger(puppet_trigger, user_provided_input=false) trigger = dummy_time_trigger if user_provided_input self.fail "'enabled' is read-only on triggers" if puppet_trigger.has_key?('enabled') self.fail "'index' is read-only on triggers" if puppet_trigger.has_key?('index') end puppet_trigger.delete('index') if puppet_trigger.delete('enabled') == false trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED else trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED end extra_keys = puppet_trigger.keys.sort - ['schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week'] self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] case puppet_trigger['schedule'] when 'daily' trigger['trigger_type'] = Win32::TaskScheduler::DAILY trigger['type'] = { 'days_interval' => Integer(puppet_trigger['every'] || 1) } when 'weekly' trigger['trigger_type'] = Win32::TaskScheduler::WEEKLY trigger['type'] = { 'weeks_interval' => Integer(puppet_trigger['every'] || 1) } trigger['type']['days_of_week'] = if puppet_trigger['day_of_week'] bitfield_from_days_of_week(puppet_trigger['day_of_week']) else scheduler_days_of_week.inject(0) {|day_flags,day| day_flags |= day} end when 'monthly' trigger['type'] = { 'months' => bitfield_from_months(puppet_trigger['months'] || (1..12).to_a), } if puppet_trigger.keys.include?('on') if puppet_trigger.has_key?('day_of_week') or puppet_trigger.has_key?('which_occurrence') self.fail "Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger" end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDATE trigger['type']['days'] = bitfield_from_days(puppet_trigger['on']) elsif puppet_trigger.keys.include?('which_occurrence') or puppet_trigger.keys.include?('day_of_week') self.fail 'which_occurrence cannot be specified as an array' if puppet_trigger['which_occurrence'].is_a?(Array) %w{day_of_week which_occurrence}.each do |field| self.fail "#{field} must be specified when creating a monthly day-of-week based trigger" unless puppet_trigger.has_key?(field) end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDOW trigger['type']['weeks'] = occurrence_name_to_constant(puppet_trigger['which_occurrence']) trigger['type']['days_of_week'] = bitfield_from_days_of_week(puppet_trigger['day_of_week']) else self.fail "Don't know how to create a 'monthly' schedule with the options: #{puppet_trigger.keys.sort.join(', ')}" end when 'once' self.fail "Must specify 'start_date' when defining a one-time trigger" unless puppet_trigger['start_date'] trigger['trigger_type'] = Win32::TaskScheduler::ONCE else self.fail "Unknown schedule type: #{puppet_trigger["schedule"].inspect}" end if start_date = puppet_trigger['start_date'] start_date = Date.parse(start_date) self.fail "start_date must be on or after 1753-01-01" unless start_date >= Date.new(1753, 1, 1) trigger['start_year'] = start_date.year trigger['start_month'] = start_date.month trigger['start_day'] = start_date.day end start_time = Time.parse(puppet_trigger['start_time']) trigger['start_hour'] = start_time.hour trigger['start_minute'] = start_time.min trigger end def validate_trigger(value) value = [value] unless value.is_a?(Array) # translate_hash_to_trigger handles the same validation that we # would be doing here at the individual trigger level. value.each {|t| translate_hash_to_trigger(t, true)} true end private def bitfield_from_months(months) bitfield = 0 months = [months] unless months.is_a?(Array) months.each do |month| integer_month = Integer(month) rescue nil self.fail 'Month must be specified as an integer in the range 1-12' unless integer_month == month.to_f and integer_month.between?(1,12) bitfield |= scheduler_months[integer_month - 1] end bitfield end def bitfield_from_days(days) bitfield = 0 days = [days] unless days.is_a?(Array) days.each do |day| # The special "day" of 'last' is represented by day "number" # 32. 'last' has the special meaning of "the last day of the # month", no matter how many days there are in the month. day = 32 if day == 'last' integer_day = Integer(day) self.fail "Day must be specified as an integer in the range 1-31, or as 'last'" unless integer_day = day.to_f and integer_day.between?(1,32) bitfield |= 1 << integer_day - 1 end bitfield end def bitfield_from_days_of_week(days_of_week) bitfield = 0 days_of_week = [days_of_week] unless days_of_week.is_a?(Array) days_of_week.each do |day_of_week| bitfield |= day_of_week_name_to_constant(day_of_week) end bitfield end def months_from_bitfield(bitfield) months = [] scheduler_months.each do |month| if bitfield & month != 0 months << month_constant_to_number(month) end end months end def days_from_bitfield(bitfield) days = [] i = 0 while bitfield > 0 if bitfield & 1 > 0 # Day 32 has the special meaning of "the last day of the # month", no matter how many days there are in the month. days << (i == 31 ? 'last' : i + 1) end bitfield = bitfield >> 1 i += 1 end days end def days_of_week_from_bitfield(bitfield) days_of_week = [] scheduler_days_of_week.each do |day_of_week| if bitfield & day_of_week != 0 days_of_week << day_of_week_constant_to_name(day_of_week) end end days_of_week end def scheduler_trigger_types [ Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE ] end def scheduler_days_of_week [ Win32::TaskScheduler::SUNDAY, Win32::TaskScheduler::MONDAY, Win32::TaskScheduler::TUESDAY, Win32::TaskScheduler::WEDNESDAY, Win32::TaskScheduler::THURSDAY, Win32::TaskScheduler::FRIDAY, Win32::TaskScheduler::SATURDAY ] end def scheduler_months [ Win32::TaskScheduler::JANUARY, Win32::TaskScheduler::FEBRUARY, Win32::TaskScheduler::MARCH, Win32::TaskScheduler::APRIL, Win32::TaskScheduler::MAY, Win32::TaskScheduler::JUNE, Win32::TaskScheduler::JULY, Win32::TaskScheduler::AUGUST, Win32::TaskScheduler::SEPTEMBER, Win32::TaskScheduler::OCTOBER, Win32::TaskScheduler::NOVEMBER, Win32::TaskScheduler::DECEMBER ] end def scheduler_occurrences [ Win32::TaskScheduler::FIRST_WEEK, Win32::TaskScheduler::SECOND_WEEK, Win32::TaskScheduler::THIRD_WEEK, Win32::TaskScheduler::FOURTH_WEEK, Win32::TaskScheduler::LAST_WEEK ] end def day_of_week_constant_to_name(constant) case constant when Win32::TaskScheduler::SUNDAY; 'sun' when Win32::TaskScheduler::MONDAY; 'mon' when Win32::TaskScheduler::TUESDAY; 'tues' when Win32::TaskScheduler::WEDNESDAY; 'wed' when Win32::TaskScheduler::THURSDAY; 'thurs' when Win32::TaskScheduler::FRIDAY; 'fri' when Win32::TaskScheduler::SATURDAY; 'sat' end end def day_of_week_name_to_constant(name) case name when 'sun'; Win32::TaskScheduler::SUNDAY when 'mon'; Win32::TaskScheduler::MONDAY when 'tues'; Win32::TaskScheduler::TUESDAY when 'wed'; Win32::TaskScheduler::WEDNESDAY when 'thurs'; Win32::TaskScheduler::THURSDAY when 'fri'; Win32::TaskScheduler::FRIDAY when 'sat'; Win32::TaskScheduler::SATURDAY end end def month_constant_to_number(constant) month_num = 1 while constant >> month_num - 1 > 1 month_num += 1 end month_num end def occurrence_constant_to_name(constant) case constant when Win32::TaskScheduler::FIRST_WEEK; 'first' when Win32::TaskScheduler::SECOND_WEEK; 'second' when Win32::TaskScheduler::THIRD_WEEK; 'third' when Win32::TaskScheduler::FOURTH_WEEK; 'fourth' when Win32::TaskScheduler::LAST_WEEK; 'last' end end def occurrence_name_to_constant(name) case name when 'first'; Win32::TaskScheduler::FIRST_WEEK when 'second'; Win32::TaskScheduler::SECOND_WEEK when 'third'; Win32::TaskScheduler::THIRD_WEEK when 'fourth'; Win32::TaskScheduler::FOURTH_WEEK when 'last'; Win32::TaskScheduler::LAST_WEEK end end end diff --git a/lib/puppet/type/scheduled_task.rb b/lib/puppet/type/scheduled_task.rb index d83adcb26..4a0fe4e66 100644 --- a/lib/puppet/type/scheduled_task.rb +++ b/lib/puppet/type/scheduled_task.rb @@ -1,222 +1,168 @@ require 'puppet/util' Puppet::Type.newtype(:scheduled_task) do include Puppet::Util - @doc = "Installs and manages Windows Scheduled Tasks. All fields - except the name, command, and start_time are optional; specifying - no repetition parameters will result in a task that runs once on - the start date. - - Examples: - - # Create a task that will fire on August 31st, 2011 at 8am in - # the system's time-zone. - scheduled_task { 'One-shot task': - ensure => present, - enabled => true, - command => 'C:\path\to\command.exe', - arguments => '/flags /to /pass', - trigger => { - schedule => once, - start_date => '2011-08-31', # Defaults to 'today' - start_time => '08:00', # Must be specified - } - } - - # Create a task that will fire every other day at 8am in the - # system's time-zone, starting August 31st, 2011. - scheduled_task { 'Daily task': - ensure => present, - enabled => true, - command => 'C:\path\to\command.exe', - arguments => '/flags /to /pass', - trigger => { - schedule => daily, - every => 2 # Defaults to 1 - start_date => '2011-08-31', # Defaults to 'today' - start_time => '08:00', # Must be specified - } - } - - # Create a task that will fire at 8am Monday every third week, - # starting after August 31st, 2011. - scheduled_task { 'Weekly task': - ensure => present, - enabled => true, - command => 'C:\path\to\command.exe', - arguments => '/flags /to /pass', - trigger => { - schedule => weekly, - every => 3, # Defaults to 1 - start_date => '2011-08-31' # Defaults to 'today' - start_time => '08:00', # Must be specified - day_of_week => [mon], # Defaults to all - } - } - - # Create a task that will fire at 8am on the 1st, 15th, and last - # day of the month in January, March, May, July, September, and - # November starting August 31st, 2011. - scheduled_task { 'Monthly date task': - ensure => present, - enabled => true, - command => 'C:\path\to\command.exe', - arguments => '/flags /to /pass', - trigger => { - schedule => monthly, - start_date => '2011-08-31', # Defaults to 'today' - start_time => '08:00', # Must be specified - months => [1,3,5,7,9,11], # Defaults to all - on => [1, 15, last], # Must be specified - } - } - - # Create a task that will fire at 8am on the first Monday of the - # month for January, March, and May, after August 31st, 2011. - scheduled_task { 'Monthly day of week task': - enabled => true, - ensure => present, - command => 'C:\path\to\command.exe', - arguments => '/flags /to /pass', - trigger => { - schedule => monthly, - start_date => '2011-08-31', # Defaults to 'today' - start_time => '08:00', # Must be specified - months => [1,3,5], # Defaults to all - which_occurrence => first, # Must be specified - day_of_week => [mon], # Must be specified - } - }" + @doc = "Installs and manages Windows Scheduled Tasks. All attributes + except `name`, `command`, and `trigger` are optional; see the description + of the `trigger` attribute for details on setting schedules." ensurable newproperty(:enabled) do - desc "Whether the triggers for this task are enabled. This only - supports enabling or disabling all of the triggers for a task, - not enabling or disabling them on an individual basis." + desc "Whether the triggers for this task should be enabled. This attribute + affects every trigger for the task; triggers cannot be enabled or + disabled individually." newvalue(:true, :event => :task_enabled) newvalue(:false, :event => :task_disabled) defaultto(:true) end newparam(:name) do desc "The name assigned to the scheduled task. This will uniquely identify the task on the system." isnamevar end newproperty(:command) do - desc "The full path to the application to be run, without any - arguments." + desc "The full path to the application to run, without any arguments." validate do |value| raise Puppet::Error.new('Must be specified using an absolute path.') unless absolute_path?(value) end end newproperty(:working_dir) do - desc "The full path of the directory in which to start the - command" + desc "The full path of the directory in which to start the command." validate do |value| raise Puppet::Error.new('Must be specified using an absolute path.') unless absolute_path?(value) end end newproperty(:arguments, :array_matching => :all) do - desc "The optional arguments to pass to the command." + desc "Any arguments or flags that should be passed to the command. Multiple arguments + can be specified as an array or as a space-separated string." end newproperty(:user) do desc "The user to run the scheduled task as. Please note that not all security configurations will allow running a scheduled task as 'SYSTEM', and saving the scheduled task under these conditions will fail with a reported error of 'The operation completed successfully'. It is recommended that you either choose another user to run the scheduled task, or alter the security policy to allow v1 scheduled tasks to run as the 'SYSTEM' account. Defaults to 'SYSTEM'." defaultto :system def insync?(current) provider.user_insync?(current, @should) end end newparam(:password) do - desc "The password for the user specified in the 'user' property. + desc "The password for the user specified in the 'user' attribute. This is only used if specifying a user other than 'SYSTEM'. Since there is no way to retrieve the password used to set the account information for a task, this parameter will not be used to determine if a scheduled task is in sync or not." end newproperty(:trigger, :array_matching => :all) do - desc "This is a hash defining the properties of the trigger used - to fire the scheduled task. The one key that is always required - is 'schedule', which can be one of 'daily', 'weekly', or - 'monthly'. The other valid & required keys depend on the value - of schedule. - - When schedule is 'daily', you can specify a value for 'every' - which specifies that the task will trigger every N days. If - 'every' is not specified, it defaults to 1 (running every day). - - When schedule is 'weekly', you can specify values for 'every', - and 'day_of_week'. 'every' has similar behavior as when - specified for 'daily', though it repeats every N weeks, instead - of every N days. 'day_of_week' is used to specify on which days - of the week the task should be run. This can be specified as an - array where the possible values are 'mon', 'tues', 'wed', - 'thurs', 'fri', 'sat', and 'sun', or as the string 'all'. The - default is 'all'. - - When schedule is 'monthly', the syntax depends on whether you - wish to specify the trigger using absolute, or relative dates. - In either case, you can specify which months this trigger - applies to using 'months', and specifying an array of integer - months. 'months' defaults to all months. - - When specifying a monthly schedule with absolute dates, 'on' - must be provided as an array of days (1-31, or the special value - 'last' which will always be the last day of the month). - - When specifying a monthly schedule with relative dates, - 'which_occurrence', and 'day_of_week' must be specified. The - possible values for 'which_occurrence' are 'first', 'second', - 'third', 'fourth', 'fifth', and 'last'. 'day_of_week' is an - array where the possible values are 'mon', 'tues', 'wed', - 'thurs', 'fri', 'sat', and 'sun'. These combine to be able to - specify things like: The task should run on the first Monday of - the specified month(s)." + desc <<-EOT + One or more triggers defining when the task should run. A single trigger is + represented as a hash, and multiple triggers can be specified with an array of + hashes. + + A trigger can contain the following keys: + + * For all triggers: + * `schedule` **(Required)** --- The schedule type. Valid values are + `daily`, `weekly`, `monthly`, or `once`. + * `start_time` **(Required)** --- The time of day when the trigger should + first become active. Several time formats will work, but we + suggest 24-hour time formatted as HH:MM. + * `start_date` --- The date when the trigger should first become active. + Defaults to "today." Several date formats will work, including + special dates like "today," but we suggest formatting dates as + YYYY-MM-DD. + * For daily triggers: + * `every` --- How often the task should run, as a number of days. Defaults + to 1. ("2" means every other day, "3" means every three days, etc.) + * For weekly triggers: + * `every` --- How often the task should run, as a number of weeks. Defaults + to 1. ("2" means every other week, "3" means every three weeks, etc.) + * `day_of_week` --- Which days of the week the task should run, as an array. + Defaults to all days. Each day must be one of `mon`, `tues`, + `wed`, `thurs`, `fri`, `sat`, `sun`, or `all`. + * For monthly-by-date triggers: + * `months` --- Which months the task should run, as an array. Defaults to + all months. Each month must be an integer between 1 and 12. + * `on` **(Required)** --- Which days of the month the task should run, + as an array. Each day must beeither an integer between 1 and 31, + or the special value `last,` which is always the last day of the month. + * For monthly-by-weekday triggers: + * `months` --- Which months the task should run, as an array. Defaults to + all months. Each month must be an integer between 1 and 12. + * `day_of_week` **(Required)** --- Which day of the week the task should + run, as an array with only one element. Each day must be one of `mon`, + `tues`, `wed`, `thurs`, `fri`, `sat`, `sun`, or `all`. + * `which_occurrence` **(Required)** --- The occurrence of the chosen weekday + when the task should run. Must be one of `first`, `second`, `third`, + `fourth`, `fifth`, or `last`. + + Examples: + + # Run at 8am on the 1st, 15th, and last day of the month in January, March, + # May, July, September, and November, starting after August 31st, 2011. + trigger => { + schedule => monthly, + start_date => '2011-08-31', # Defaults to 'today' + start_time => '08:00', # Must be specified + months => [1,3,5,7,9,11], # Defaults to all + on => [1, 15, last], # Must be specified + } + + # Run at 8am on the first Monday of the month for January, March, and May, + # starting after August 31st, 2011. + trigger => { + schedule => monthly, + start_date => '2011-08-31', # Defaults to 'today' + start_time => '08:00', # Must be specified + months => [1,3,5], # Defaults to all + which_occurrence => first, # Must be specified + day_of_week => [mon], # Must be specified + } + + EOT validate do |value| provider.validate_trigger(value) end def insync?(current) provider.trigger_insync?(current, @should) end def should_to_s(new_value=@should) self.class.format_value_for_display(new_value) end def is_to_s(current_value=@is) self.class.format_value_for_display(current_value) end end validate do return true if self[:ensure] == :absent if self[:arguments] and !(self[:arguments].is_a?(Array) and self[:arguments].length == 1) self.fail('Parameter arguments failed: Must be specified as a single string') end end end