diff options
author | Vasu1105 <vasundhara.jagdale@msystechnologies.com> | 2018-01-31 11:47:09 +0000 |
---|---|---|
committer | vasu1105 <vasundhara.jagdale@msystechnologies.com> | 2018-04-24 11:32:31 +0530 |
commit | 2c665b3e889381062b2faf1f28d4a41ca1a698cb (patch) | |
tree | 2d8e1fdc98a9d267c6fa403732d421e6feb6d684 | |
parent | 5ef14cc77226fe16b734ec90aead1de5d49ca7fb (diff) | |
download | chef-2c665b3e889381062b2faf1f28d4a41ca1a698cb.tar.gz |
[MSYS-752] windows task rewrite using wind32-taskscheduler
Signed-off-by: Vasu1105 <vasundhara.jagdale@msystechnologies.com>
-rw-r--r-- | Gemfile.lock | 5 | ||||
-rw-r--r-- | chef-universal-mingw32.gemspec | 1 | ||||
-rw-r--r-- | lib/chef/provider/windows_task.rb | 927 | ||||
-rw-r--r-- | lib/chef/resource/windows_task.rb | 180 | ||||
-rw-r--r-- | spec/functional/resource/windows_task_spec.rb | 1027 | ||||
-rw-r--r-- | spec/unit/provider/windows_task_spec.rb | 861 | ||||
-rw-r--r-- | spec/unit/resource/windows_task_spec.rb | 88 |
7 files changed, 1786 insertions, 1303 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index aa7ac2e51a..854d91aabb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,6 +84,7 @@ PATH win32-mutex (~> 0.4.2) win32-process (~> 0.8.2) win32-service (~> 0.8.7) + win32-taskscheduler (~> 0.4.0) windows-api (~> 0.4.4) wmi-lite (~> 1.0) @@ -343,6 +344,7 @@ GEM net-telnet sfl sslshake (1.2.0) + structured_warnings (0.3.0) syslog-logger (1.6.8) systemu (2.6.5) thor (0.20.0) @@ -397,6 +399,9 @@ GEM win32-service (0.8.10) ffi ffi-win32-extensions + win32-taskscheduler (0.4.0) + ffi + structured_warnings windows-api (0.4.4) win32-api (>= 1.4.5) winrm (2.2.3) diff --git a/chef-universal-mingw32.gemspec b/chef-universal-mingw32.gemspec index 449f909bb2..ea65edbbd6 100644 --- a/chef-universal-mingw32.gemspec +++ b/chef-universal-mingw32.gemspec @@ -15,6 +15,7 @@ gemspec.add_dependency "win32-process", "~> 0.8.2" gemspec.add_dependency "win32-service", "~> 0.8.7" gemspec.add_dependency "windows-api", "~> 0.4.4" gemspec.add_dependency "wmi-lite", "~> 1.0" +gemspec.add_dependency "win32-taskscheduler", "~> 0.4.0" gemspec.extensions << "ext/win32-eventlog/Rakefile" gemspec.files += %w{ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man} diff --git a/lib/chef/provider/windows_task.rb b/lib/chef/provider/windows_task.rb index 02b68b1296..56c031d163 100644 --- a/lib/chef/provider/windows_task.rb +++ b/lib/chef/provider/windows_task.rb @@ -20,6 +20,8 @@ require "chef/mixin/shell_out" require "rexml/document" require "iso8601" require "chef/mixin/powershell_out" +require "chef/provider" +require "win32/taskscheduler" if Chef::Platform.windows? class Chef class Provider @@ -27,542 +29,559 @@ class Chef include Chef::Mixin::ShellOut include Chef::Mixin::PowershellOut - provides :windows_task - - def load_current_resource - self.current_resource = Chef::Resource::WindowsTask.new(new_resource.name) - pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}" - - current_resource.task_name(pathed_task_name) - task_hash = load_task_hash(pathed_task_name) - - set_current_resource(task_hash) if task_hash.respond_to?(:[]) && task_hash[:TaskName] == pathed_task_name - current_resource - end - - def set_current_resource(task_hash) - current_resource.exists = true - current_resource.command(task_hash[:TaskToRun]) - current_resource.cwd(task_hash[:StartIn]) unless task_hash[:StartIn] == "N/A" - current_resource.user(task_hash[:RunAsUser]) - set_current_run_level task_hash[:run_level] - set_current_frequency task_hash - current_resource.day(task_hash[:day]) if task_hash[:day] - current_resource.months(task_hash[:months]) if task_hash[:months] - set_current_idle_time(task_hash[:idle_time]) if task_hash[:idle_time] - current_resource.random_delay(task_hash[:random_delay]) if task_hash[:random_delay] - # schtask sets execution_time_limit as PT72H by default - current_resource.execution_time_limit(task_hash[:execution_time_limit] || "PT72H") - current_resource.status = :running if task_hash[:Status] == "Running" - current_resource.enabled = true if task_hash[:ScheduledTaskState] == "Enabled" - current_resource.start_time = task_hash[:StartTime] if task_hash[:StartTime] - current_resource.start_day = task_hash[:StartDate] if task_hash[:StartDate] - end + if Chef::Platform.windows? + include Win32 + + provides :windows_task + + MONTHS = { + JAN: TaskScheduler::JANUARY, + FEB: TaskScheduler::FEBRUARY, + MAR: TaskScheduler::MARCH, + APR: TaskScheduler::APRIL, + MAY: TaskScheduler::MAY, + JUN: TaskScheduler::JUNE, + JUL: TaskScheduler::JULY, + AUG: TaskScheduler::AUGUST, + SEP: TaskScheduler::SEPTEMBER, + OCT: TaskScheduler::OCTOBER, + NOV: TaskScheduler::NOVEMBER, + DEC: TaskScheduler::DECEMBER + } - # This method checks if task and command attributes exist since those two are mandatory attributes to create a schedules task. - def basic_validation - validate = [] - validate << "Command" if new_resource.command.nil? || new_resource.command.empty? - validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty? - return true if validate.empty? - raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(', ')}' option cannot be empty" - end + DAYS_OF_WEEK = { MON: TaskScheduler::MONDAY, + TUE: TaskScheduler::TUESDAY, + WED: TaskScheduler::WEDNESDAY, + THU: TaskScheduler::THURSDAY, + FRI: TaskScheduler::FRIDAY, + SAT: TaskScheduler::SATURDAY, + SUN: TaskScheduler::SUNDAY } + + WEEKS_OF_MONTH = { + FIRST: TaskScheduler::FIRST_WEEK, + SECOND: TaskScheduler::SECOND_WEEK, + THIRD: TaskScheduler::THIRD_WEEK, + FOURTH: TaskScheduler::FOURTH_WEEK + } - # get array of windows task resource attributes - def resource_attributes - %w{ command user run_level cwd frequency_modifier frequency idle_time random_delay execution_time_limit start_day start_time } - end + DAYS_OF_MONTH = { + 1 => TaskScheduler::TASK_FIRST, + 2 => TaskScheduler::TASK_SECOND, + 3 => TaskScheduler::TASK_THIRD, + 4 => TaskScheduler::TASK_FOURTH, + 5 => TaskScheduler::TASK_FIFTH, + 6 => TaskScheduler::TASK_SIXTH, + 7 => TaskScheduler::TASK_SEVENTH, + 8 => TaskScheduler::TASK_EIGHTH, + 9 => TaskScheduler::TASK_NINETH, + 10 => TaskScheduler::TASK_TENTH, + 11 => TaskScheduler::TASK_ELEVENTH, + 12 => TaskScheduler::TASK_TWELFTH, + 13 => TaskScheduler::TASK_THIRTEENTH, + 14 => TaskScheduler::TASK_FOURTEENTH, + 15 => TaskScheduler::TASK_FIFTEENTH, + 16 => TaskScheduler::TASK_SIXTEENTH, + 17 => TaskScheduler::TASK_SEVENTEENTH, + 18 => TaskScheduler::TASK_EIGHTEENTH, + 19 => TaskScheduler::TASK_NINETEENTH, + 20 => TaskScheduler::TASK_TWENTIETH, + 21 => TaskScheduler::TASK_TWENTY_FIRST, + 22 => TaskScheduler::TASK_TWENTY_SECOND, + 23 => TaskScheduler::TASK_TWENTY_THIRD, + 24 => TaskScheduler::TASK_TWENTY_FOURTH, + 25 => TaskScheduler::TASK_TWENTY_FIFTH, + 26 => TaskScheduler::TASK_TWENTY_SIXTH, + 27 => TaskScheduler::TASK_TWENTY_SEVENTH, + 28 => TaskScheduler::TASK_TWENTY_EIGHTH, + 29 => TaskScheduler::TASK_TWENTY_NINTH, + 30 => TaskScheduler::TASK_THIRTYETH, + 31 => TaskScheduler::TASK_THIRTY_FIRST + } - def action_create - if current_resource.exists - logger.trace "#{new_resource} task exists" - if !(task_need_update? || new_resource.force) - logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do" - return - end - # Setting the attributes of new_resource as current_resource. - # This is required to handle update scenarios when the user specifies - # only those attributes in the recipe which require update - resource_attributes.each do |attribute| - new_resource_attribute = new_resource.send(attribute) - current_resource_attribute = current_resource.send(attribute) - # We accept start_day in mm/dd/yyyy format only. Hence while copying the start_day from system to new_resource.start_day, - # we are converting from system date format to mm/dd/yyyy - current_resource_attribute = convert_system_date_to_mm_dd_yyyy(current_resource_attribute) if attribute == "start_day" && current_resource_attribute != "N/A" - # Convert start_time into 24hr time format - current_resource_attribute = DateTime.parse(current_resource_attribute).strftime("%H:%M") if attribute == "start_time" && current_resource_attribute != "N/A" - new_resource.send("#{attribute}=", current_resource_attribute ) if current_resource_attribute && new_resource_attribute.nil? + def load_current_resource + @current_resource = Chef::Resource::WindowsTask.new(new_resource.name) + task = TaskScheduler.new + if task.exists?(new_resource.task_name) + @current_resource.exists = true + task.get_task(new_resource.task_name) + @current_resource.task = task + pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}" + @current_resource.task_name(pathed_task_name) + else + @current_resource.exists = false end + @current_resource end - basic_validation - options = {} - options["F"] = "" if new_resource.force || task_need_update? - if schedule == :none - options["SC"] = :once - options["ST"] = "00:00" - options["SD"] = convert_user_date_to_system_date "12/12/2012" - else - options["SC"] = schedule - options["ST"] = new_resource.start_time unless new_resource.start_time.nil? || new_resource.start_time == "N/A" - options["SD"] = convert_user_date_to_system_date new_resource.start_day unless new_resource.start_day.nil? || new_resource.start_day == "N/A" - end - options["MO"] = new_resource.frequency_modifier if frequency_modifier_allowed - options["I"] = new_resource.idle_time unless new_resource.idle_time.nil? - options["TR"] = new_resource.command - options["RU"] = new_resource.user - options["RP"] = new_resource.password if use_password? - options["RL"] = "HIGHEST" if new_resource.run_level == :highest - options["IT"] = "" if new_resource.interactive_enabled - options["D"] = new_resource.day if new_resource.day - options["M"] = new_resource.months unless new_resource.months.nil? - run_schtasks "CREATE", options - xml_options = [] - xml_options << "cwd" if new_resource.cwd - xml_options << "random_delay" if new_resource.random_delay - xml_options << "execution_time_limit" if new_resource.execution_time_limit - - converge_by("#{new_resource} task created") do - update_task_xml(xml_options) unless xml_options.empty? - end - end - def action_run - if current_resource.exists - logger.trace "#{new_resource} task exists" - if current_resource.status == :running - logger.info "#{new_resource} task is currently running, skipping run" + def action_create + if current_resource.exists + logger.trace "#{new_resource} task exist." + unless (task_needs_update?(current_resource.task)) || (new_resource.force) + logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do" + return + end + + # if start_day and start_time is not set by user current date and time will be set while updating any property + set_start_day_and_time unless new_resource.frequency == :none + update_task(current_resource.task) else - converge_by("run scheduled task #{new_resource}") do - run_schtasks "RUN" + basic_validation + set_start_day_and_time + task = TaskScheduler.new + if new_resource.frequency == :none + task.new_work_item(new_resource.task_name, {}) + task.activate(new_resource.task_name) + else + task.new_work_item(new_resource.task_name, trigger) + end + task.application_name = new_resource.command + task.working_directory = new_resource.cwd if new_resource.cwd + task.configure_settings(config_settings) + task.configure_principals(principal_settings) + task.set_account_information(new_resource.user, new_resource.password) + task.creator = new_resource.user + converge_by("#{new_resource} task created") do + task.activate(new_resource.task_name) end end - else - logger.warn "#{new_resource} task does not exist - nothing to do" end - end - def action_delete - if current_resource.exists - logger.trace "#{new_resource} task exists" - converge_by("delete scheduled task #{new_resource}") do - # always need to force deletion - run_schtasks "DELETE", "F" => "" + def action_run + if current_resource.exists + logger.trace "#{new_resource} task exists" + if current_resource.task.status == "running" + logger.info "#{new_resource} task is currently running, skipping run" + else + converge_by("run scheduled task #{new_resource}") do + current_resource.task.run + end + end + else + logger.warn "#{new_resource} task does not exist - nothing to do" end - else - logger.warn "#{new_resource} task does not exist - nothing to do" end - end - def action_end - if current_resource.exists - logger.trace "#{new_resource} task exists" - if current_resource.status != :running - logger.trace "#{new_resource} is not running - nothing to do" - else - converge_by("#{new_resource} task ended") do - run_schtasks "END" + def action_delete + if current_resource.exists + logger.trace "#{new_resource} task exists" + converge_by("delete scheduled task #{new_resource}") do + ts = TaskScheduler.new + ts.delete(current_resource.name) end + else + logger.warn "#{new_resource} task does not exist - nothing to do" end - else - logger.warn "#{new_resource} task does not exist - nothing to do" end - end - def action_enable - if current_resource.exists - logger.trace "#{new_resource} task exists" - if current_resource.enabled - logger.trace "#{new_resource} already enabled - nothing to do" - else - converge_by("#{new_resource} task enabled") do - run_schtasks "CHANGE", "ENABLE" => "" + def action_end + if current_resource.exists + logger.trace "#{new_resource} task exists" + if current_resource.task.status != "running" + logger.trace "#{new_resource} is not running - nothing to do" + else + converge_by("#{new_resource} task ended") do + current_resource.task.stop + end end + else + logger.warn "#{new_resource} task does not exist - nothing to do" end - else - logger.fatal "#{new_resource} task does not exist - nothing to do" - raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable" end - end - def action_disable - if current_resource.exists - logger.info "#{new_resource} task exists" - if current_resource.enabled - converge_by("#{new_resource} task disabled") do - run_schtasks "CHANGE", "DISABLE" => "" + def action_enable + if current_resource.exists + logger.trace "#{new_resource} task exists" + if current_resource.task.status == "not scheduled" + converge_by("#{new_resource} task enabled") do + #TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here + run_schtasks "CHANGE", "ENABLE" => "" + end + else + logger.trace "#{new_resource} already enabled - nothing to do" end else - logger.warn "#{new_resource} already disabled - nothing to do" + logger.fatal "#{new_resource} task does not exist - nothing to do" + raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable" end - else - logger.warn "#{new_resource} task does not exist - nothing to do" end - end - private - - # rubocop:disable Style/StringLiteralsInInterpolation - def run_schtasks(task_action, options = {}) - cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" " - options.each_key do |option| - unless option == "TR" - cmd += "/#{option} " - cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == "" + def action_disable + if current_resource.exists + logger.info "#{new_resource} task exists" + if %w{ready running}.include?(current_resource.task.status) + converge_by("#{new_resource} task disabled") do + #TODO: in win32-taskscheduler there is no method whcih disbales the task so currently calling disable with schtasks.exe + run_schtasks "CHANGE", "DISABLE" => "" + end + else + logger.warn "#{new_resource} already disabled - nothing to do" + end + else + logger.warn "#{new_resource} task does not exist - nothing to do" end end - # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value - if options["TR"] - cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE" - end - logger.trace("running: ") - logger.trace(" #{cmd}") - shell_out!(cmd, returns: [0]) - end - # rubocop:enable Style/StringLiteralsInInterpolation - - def task_need_update? - return true if (new_resource.command && - current_resource.command != new_resource.command.tr("'", '"')) || - current_resource.user != new_resource.user || - current_resource.run_level != new_resource.run_level || - current_resource.cwd != new_resource.cwd || - current_resource.frequency_modifier != new_resource.frequency_modifier || - current_resource.frequency != new_resource.frequency || - current_resource.idle_time != new_resource.idle_time || - random_delay_updated? || execution_time_limit_updated? || - (new_resource.start_day && new_resource.start_day != "N/A" && start_day_updated?) || - (new_resource.start_time && new_resource.start_time != "N/A" && start_time_updated?) - begin - return true if new_resource.day.to_s.casecmp(current_resource.day.to_s) != 0 || - new_resource.months.to_s.casecmp(current_resource.months.to_s) != 0 - rescue - logger.trace "caught a raise in task_needs_update?" - end - false - end + alias_method :action_change, :action_create - # Comparing random_delay values using ISO8601::Duration object Ref: https://github.com/arnau/ISO8601/blob/master/lib/iso8601/duration.rb#L18-L23 - # di = ISO8601::Duration.new(65707200) - # ds = ISO8601::Duration.new('P65707200S') - # dp = ISO8601::Duration.new('P2Y1MT2H') - # di == dp # => true - # di == ds # => true - def random_delay_updated? - if new_resource.random_delay.nil? - false - elsif current_resource.random_delay.nil? && new_resource.random_delay == "PT0S" # when user sets random_dealy to 0 sec - false - elsif current_resource.random_delay.nil? - true - else - ISO8601::Duration.new(current_resource.random_delay) != ISO8601::Duration.new(new_resource.random_delay) - end - end + private - # Comparing execution_time_limit values using Ref: https://github.com/arnau/ISO8601/blob/master/lib/iso8601/duration.rb#L18-L23 - def execution_time_limit_updated? - if new_resource.execution_time_limit.nil? - false - elsif current_resource.execution_time_limit.nil? && new_resource.execution_time_limit == "PT0S" # when user sets random_dealy to 0 sec - false - elsif current_resource.execution_time_limit.nil? - true - else - ISO8601::Duration.new(current_resource.execution_time_limit) != ISO8601::Duration.new(new_resource.execution_time_limit) + def set_start_day_and_time + new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day + new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time end - end - - def start_day_updated? - current_day = DateTime.strptime(current_resource.start_day, convert_system_date_format_to_ruby_date_format) - new_day = parse_day(new_resource.start_day) - current_day != new_day - end - def start_time_updated? - time = DateTime.parse(current_resource.start_time).strftime("%H:%M") - time != new_resource.start_time - end + def update_task(task) + converge_by("#{new_resource} task updated") do + task.set_account_information(new_resource.user, new_resource.password) + task.application_name = new_resource.command if new_resource.command + task.working_directory = new_resource.cwd if new_resource.cwd + task.trigger = trigger unless new_resource.frequency == :none + task.configure_settings(config_settings) + task.creator = new_resource.user + task.configure_principals(principal_settings) + end + end - def convert_user_date_to_system_date(date_in_string) - parse_day(date_in_string).strftime(convert_system_date_format_to_ruby_long_date) - end + def trigger + start_month, start_day, start_year = new_resource.start_day.to_s.split("/") + start_hour, start_minute = new_resource.start_time.to_s.split(":") + #TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error. + trigger_hash = { + start_year: start_year.to_i, + start_month: start_month.to_i, + start_day: start_day.to_i, + start_hour: start_hour.to_i, + start_minute: start_minute.to_i, + end_month: 0, + end_day: 0, + end_year: 0, + trigger_type: trigger_type, + type: type, + random_minutes_interval: new_resource.random_delay + } + + if new_resource.frequency == :minute + trigger_hash[:minutes_interval] = new_resource.frequency_modifier + end - def convert_system_date_format_to_ruby_long_date - date_format = get_system_short_date_format.dup - date_format.sub!("MMM", "%m") - common_date_format_conversion(date_format) - date_format.sub!("yy", "%Y") - date_format - end + if new_resource.frequency == :hourly + minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i) + trigger_hash[:minutes_interval] = minutes + end - def convert_system_date_format_to_ruby_date_format - date_format = get_system_short_date_format.dup - date_format.sub!("MMM", "%b") - common_date_format_conversion(date_format) - date_format.sub!("yy", "%y") - date_format - end + if new_resource.minutes_interval + trigger_hash[:minutes_interval] = new_resource.minutes_interval + end - def common_date_format_conversion(date_format) - date_format.sub!("dd", "d") - date_format.sub!("d", "%d") - date_format.sub!("MM", "%m") - date_format.sub!("M", "%m") - date_format.sub!("yyyy", "%Y") - end + if new_resource.minutes_duration + trigger_hash[:minutes_duration] = new_resource.minutes_duration + end - def get_system_short_date_format - return @system_short_date_format if @system_short_date_format - logger.trace "Finding system date format" - task_script = <<-EOH - [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - [Globalization.Cultureinfo]::CurrentCulture.DateTimeFormat.ShortDatePattern - EOH - @system_short_date_format = powershell_out(task_script).stdout.force_encoding("UTF-8").gsub(/[\s+\uFEFF]/, "") - @system_short_date_format - end + if trigger_type == TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier) + trigger_hash[:run_on_last_week_of_month] = true + else + trigger_hash[:run_on_last_week_of_month] = false + end - def convert_system_date_to_mm_dd_yyyy(system_date) - system_date_format = convert_system_date_format_to_ruby_date_format - unless system_date_format == "%m/%d/%Y" - system_date = Date.strptime(system_date, system_date_format).strftime("%m/%d/%Y") + if trigger_type == TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day) + trigger_hash[:run_on_last_day_of_month] = true + else + trigger_hash[:run_on_last_day_of_month] = false + end + trigger_hash end - system_date - end - def update_task_xml(options = []) - # random_delay xml element is different for different frequencies - random_delay_xml_element = { - :minute => "Triggers/TimeTrigger/RandomDelay", - :hourly => "Triggers/TimeTrigger/RandomDelay", - :once => "Triggers/TimeTrigger/RandomDelay", - :daily => "Triggers/CalendarTrigger/RandomDelay", - :weekly => "Triggers/CalendarTrigger/RandomDelay", - :monthly => "Triggers/CalendarTrigger/RandomDelay", - :none => "Triggers", - } - - xml_element_mapping = { - "cwd" => "Actions/Exec/WorkingDirectory", - "random_delay" => random_delay_xml_element[new_resource.frequency], - "execution_time_limit" => "Settings/ExecutionTimeLimit", - } + def frequency_modifier_contains_last_week?(frequency_modifier) + frequency_modifier = frequency_modifier.to_s.split(",") + frequency_modifier.map! { |value| value.strip.upcase } + frequency_modifier.include?("LAST") + end - logger.trace "looking for existing tasks" + def day_includes_last_or_lastday?(day) + day = day.to_s.split(",") + day.map! { |value| value.strip.upcase } + day.include?("LAST") || day.include?("LASTDAY") + end - task_script = <<-EOH - [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - schtasks /Query /TN \"#{new_resource.task_name}\" /XML - EOH - xml_cmd = powershell_out(task_script) + def convert_hours_in_minutes(hours) + hours.to_i * 60 if hours + end - return if xml_cmd.exitstatus != 0 + #TODO : Try to optimize this method + # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency + # is not gettting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task + def task_needs_update?(task) + flag = false + if new_resource.frequency == :none + flag = (task.account_information != new_resource.user || + task.application_name != new_resource.command || + task.principals[:run_level] != run_level) + else + current_task_trigger = task.trigger(0) + new_task_trigger = trigger + flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle + flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil? + + # if trigger not found updating the task to add the trigger + if current_task_trigger.nil? + flag = true + else + flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true || + start_time_updated?(current_task_trigger, new_task_trigger) == true || + current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] || + current_task_trigger[:type] != new_task_trigger[:type] || + current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i || + current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i || + task.account_information != new_resource.user || + task.application_name != new_resource.command || + task.working_directory != new_resource.cwd.to_s || + task.principals[:logon_type] != logon_type || + task.principals[:run_level] != run_level + + if trigger_type == TaskScheduler::MONTHLYDATE + flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month] + end + + if trigger_type == TaskScheduler::MONTHLYDOW + flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month] + end + end + end + flag + end - doc = REXML::Document.new(xml_cmd.stdout) + def start_day_updated?(current_task_trigger, new_task_trigger) + ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] || + current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] || + current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) ) + end - if new_resource.frequency == :none - doc.root.elements.delete(xml_element_mapping["random_delay"]) - trigger_element = REXML::Element.new(xml_element_mapping["random_delay"]) - doc.root.elements.add(trigger_element) + def start_time_updated?(current_task_trigger, new_task_trigger) + ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] || + current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) ) end - options.each do |option| - logger.trace "Removing former #{option} if any" - doc.root.elements.delete(xml_element_mapping[option]) - option_value = new_resource.send("#{option}") - - if option_value - logger.trace "Setting #{option} as #{option_value}" - split_xml_path = xml_element_mapping[option].split("/") # eg. if xml_element_mapping[option] = "Actions/Exec/WorkingDirectory" - element_name = split_xml_path.last # element_name = "WorkingDirectory" - cwd_element = REXML::Element.new(element_name) - cwd_element.add_text(option_value) - element_root = (split_xml_path - [element_name]).join("/") # element_root = 'Actions/Exec' - exec_element = doc.root.elements[element_root] - exec_element.add_element(cwd_element) + def trigger_type + case new_resource.frequency + when :once, :minute, :hourly + TaskScheduler::ONCE + when :daily + TaskScheduler::DAILY + when :weekly + TaskScheduler::WEEKLY + when :monthly + # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow + # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx + new_resource.frequency_modifier.to_i.between?(1, 12) ? TaskScheduler::MONTHLYDATE : TaskScheduler::MONTHLYDOW + when :on_idle + TaskScheduler::ON_IDLE + when :onstart + TaskScheduler::AT_SYSTEMSTART + when :on_logon + TaskScheduler::AT_LOGON + else + raise ArgumentError, "Please set frequency" end end - temp_task_file = ::File.join(ENV["TEMP"], "windows_task.xml") - begin - ::File.open(temp_task_file, "w:UTF-16LE") do |f| - doc.write(f) + def type + case trigger_type + when TaskScheduler::ONCE + { once: nil } + when TaskScheduler::DAILY + { days_interval: new_resource.frequency_modifier.to_i } + when TaskScheduler::WEEKLY + { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i } + when TaskScheduler::MONTHLYDATE + { months: months_of_year.to_i, days: days_of_month.to_i } + when TaskScheduler::MONTHLYDOW + { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i } + when TaskScheduler::ON_IDLE + # TODO: handle option for this trigger + when TaskScheduler::AT_LOGON + # TODO: handle option for this trigger + when TaskScheduler::AT_SYSTEMSTART + # TODO: handle option for this trigger end + end - options = {} - options["RU"] = new_resource.user if new_resource.user - options["RP"] = new_resource.password if new_resource.password - options["IT"] = "" if new_resource.interactive_enabled - options["XML"] = temp_task_file - run_schtasks("DELETE", "F" => "") - run_schtasks("CREATE", options) - ensure - ::File.delete(temp_task_file) + # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter. + def weeks_of_month + weeks_of_month = [] + if new_resource.frequency_modifier + weeks = new_resource.frequency_modifier.split(",") + weeks.map! { |week| week.to_s.strip.upcase } + weeks.delete("LAST") if weeks.include?("LAST") + weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH) + end + weeks_of_month end - end - def load_task_hash(task_name) - logger.trace "Looking for existing tasks" - - task_script = <<-EOH - [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - schtasks /Query /FO LIST /V /TN \"#{task_name}\" - EOH - - output = powershell_out(task_script).stdout.force_encoding("UTF-8") - if output.empty? - task = false - else - task = {} - - output.split("\n").map! do |line| - line.split(": ").map!(&:strip) - end.each do |field| - if field.is_a?(Array) && field[0].respond_to?(:to_sym) - key = (field - [field.last]).join(": ") - task[key.gsub(/\s+/, "").to_sym] = field.last + # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter. + def days_of_month + days_of_month = [] + if new_resource.day + days = new_resource.day.split(",") + days.map! { |day| day.to_s.strip.upcase } + days.delete("LAST") if days.include?("LAST") + days.delete("LASTDAY") if days.include?("LASTDAY") + if days - (1..31).to_a + days.each do |day| + days_of_month << DAYS_OF_MONTH[day.to_i] + end + days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0] end + else + days_of_month = DAYS_OF_MONTH[1] end + days_of_month end - task_xml = load_task_xml task_name - task.merge!(task_xml) if task && task_xml - - task - end - - def load_task_xml(task_name) - task_script = <<-EOH - [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - schtasks /Query /TN \"#{task_name}\" /XML - EOH - xml_cmd = powershell_out(task_script) - - return if xml_cmd.exitstatus != 0 - - doc = REXML::Document.new(xml_cmd.stdout) - root = doc.root - - task = {} - task[:run_level] = root.elements["Principals/Principal/RunLevel"].text if root.elements["Principals/Principal/RunLevel"] - - # for frequency = :minutes, :hourly - task[:repetition_interval] = root.elements["Triggers/TimeTrigger/Repetition/Interval"].text if root.elements["Triggers/TimeTrigger/Repetition/Interval"] + def days_of_week + if new_resource.day + #this line of code is just to support backward compatibility of wild card * + new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly + days = new_resource.day.split(",") + days.map! { |day| day.to_s.strip.upcase } + weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK) + else + # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day + if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?) + weeks_days = current_resource.task.trigger(0)[:type][:days_of_week] + else + day = get_day(new_resource.start_day).to_sym if new_resource.start_day + DAYS_OF_WEEK[day] + end + end + end - # for frequency = :daily - task[:schedule_by_day] = root.elements["Triggers/CalendarTrigger/ScheduleByDay/DaysInterval"].text if root.elements["Triggers/CalendarTrigger/ScheduleByDay/DaysInterval"] + def months_of_year + months_of_year = [] + if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months) + new_resource.months = set_months(new_resource.frequency_modifier.to_i) + end - # for frequency = :weekly - task[:schedule_by_week] = root.elements["Triggers/CalendarTrigger/ScheduleByWeek/WeeksInterval"].text if root.elements["Triggers/CalendarTrigger/ScheduleByWeek/WeeksInterval"] - if root.elements["Triggers/CalendarTrigger/ScheduleByWeek/DaysOfWeek"] - task[:day] = [] - root.elements["Triggers/CalendarTrigger/ScheduleByWeek/DaysOfWeek"].elements.each do |e| - task[:day] << e.to_s[0..3].delete("<").delete("/>") + if new_resource.months + #this line of code is just to support backward compatibility of wild card * + new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly + months = new_resource.months.split(",") + months.map! { |month| month.to_s.strip.upcase } + months_of_year = get_binary_values_from_constants(months, MONTHS) + else + MONTHS.each do |key, value| + months_of_year << MONTHS[key] + end + months_of_year = months_of_year.inject(:|) end - task[:day] = task[:day].join(", ") + months_of_year end - # for frequency = :monthly - task[:schedule_by_month] = root.elements["Triggers/CalendarTrigger/ScheduleByMonth/DaysOfMonth/Day"].text if root.elements["Triggers/CalendarTrigger/ScheduleByMonth/DaysOfMonth/Day"] - if root.elements["Triggers/CalendarTrigger/ScheduleByMonth/Months"] - task[:months] = [] - root.elements["Triggers/CalendarTrigger/ScheduleByMonth/Months"].elements.each do |e| - task[:months] << e.to_s[0..3].delete("<").delete("/>") + # This values are set for frequency_modifier set as 1-12 + # This is to give backward compatibility validated this values with earlier code and running schtask.exe + # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday-- + def set_months(frequency_modifier) + case frequency_modifier + when 1 + "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" + when 2 + "feb, apr, jun, aug, oct, dec" + when 3 + "mar, jun, sep, dec" + when 4 + "apr, aug, dec" + when 5 + "may, oct" + when 6 + "jun, dec" + when 7 + "jul" + when 8 + "aug" + when 9 + "sep" + when 10 + "oct" + when 11 + "nov" + when 12 + "dec" end - task[:months] = task[:months].join(", ") end - task[:on_logon] = true if root.elements["Triggers/LogonTrigger"] - task[:onstart] = true if root.elements["Triggers/BootTrigger"] - task[:on_idle] = true if root.elements["Triggers/IdleTrigger"] - - task[:idle_time] = root.elements["Settings/IdleSettings/Duration"].text if root.elements["Settings/IdleSettings/Duration"] && task[:on_idle] - - task[:none] = true if root.elements["Triggers/"] && root.elements["Triggers/"].entries.empty? - task[:once] = true if !(task[:repetition_interval] || task[:schedule_by_day] || task[:schedule_by_week] || task[:schedule_by_month] || task[:on_logon] || task[:onstart] || task[:on_idle] || task[:none]) - task[:execution_time_limit] = root.elements["Settings/ExecutionTimeLimit"].text if root.elements["Settings/ExecutionTimeLimit"] #by default PT72H - task[:random_delay] = root.elements["Triggers/TimeTrigger/RandomDelay"].text if root.elements["Triggers/TimeTrigger/RandomDelay"] - task[:random_delay] = root.elements["Triggers/CalendarTrigger/RandomDelay"].text if root.elements["Triggers/CalendarTrigger/RandomDelay"] - task - end - - SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze - - def use_password? - @use_password ||= !SYSTEM_USERS.include?(new_resource.user.upcase) - end - - def schedule - case new_resource.frequency - when :on_logon - "ONLOGON" - when :on_idle - "ONIDLE" - else - new_resource.frequency + def get_binary_values_from_constants(array_values, constant) + data = [] + array_values.each do |value| + value = value.to_sym + data << constant[value] + end + data.size > 1 ? data.inject(:|) : data[0] end - end - def frequency_modifier_allowed - case new_resource.frequency - when :minute, :hourly, :daily, :weekly - true - when :monthly - new_resource.months.nil? || %w{ FIRST SECOND THIRD FOURTH LAST LASTDAY }.include?(new_resource.frequency_modifier) - else - false + def run_level + case new_resource.run_level + when :highest + TaskScheduler::TASK_RUNLEVEL_HIGHEST + when :limited + TaskScheduler::TASK_RUNLEVEL_LUA + end end - end - def set_current_run_level(run_level) - case run_level - when "HighestAvailable" - current_resource.run_level(:highest) - when "LeastPrivilege" - current_resource.run_level(:limited) + #TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 formata + def config_settings + settings = { + execution_time_limit: new_resource.execution_time_limit, + enabled: true + } + settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time + settings[:run_only_if_idle] = true if new_resource.idle_time + settings end - end - def set_current_frequency(task_hash) - if task_hash[:repetition_interval] - duration = ISO8601::Duration.new(task_hash[:repetition_interval]) - if task_hash[:repetition_interval].include?("M") - current_resource.frequency(:minute) - current_resource.frequency_modifier(duration.minutes.atom.to_i) - elsif task_hash[:repetition_interval].include?("H") - current_resource.frequency(:hourly) - current_resource.frequency_modifier(duration.hours.atom.to_i) - end + def principal_settings + settings = {} + settings [:run_level] = run_level + settings[:logon_type] = logon_type + settings end - if task_hash[:schedule_by_day] - current_resource.frequency(:daily) - current_resource.frequency_modifier(task_hash[:schedule_by_day].to_i) + def logon_type + # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx + # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparision. + new_resource.password.nil? ? TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT : TaskScheduler::TASK_LOGON_PASSWORD end - if task_hash[:schedule_by_week] - current_resource.frequency(:weekly) - current_resource.frequency_modifier(task_hash[:schedule_by_week].to_i) + # This method checks if task and command attributes exist since those two are mandatory attributes to create a schedules task. + def basic_validation + validate = [] + validate << "Command" if new_resource.command.nil? || new_resource.command.empty? + validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty? + return true if validate.empty? + raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(', ')}' option cannot be empty" end - current_resource.frequency(:monthly) if task_hash[:schedule_by_month] - current_resource.frequency(:on_logon) if task_hash[:on_logon] - current_resource.frequency(:onstart) if task_hash[:onstart] - current_resource.frequency(:on_idle) if task_hash[:on_idle] - current_resource.frequency(:once) if task_hash[:once] - current_resource.frequency(:none) if task_hash[:none] - end - - def set_current_idle_time(idle_time) - duration = ISO8601::Duration.new(idle_time) - current_resource.idle_time(duration.minutes.atom.to_i) - end + # rubocop:disable Style/StringLiteralsInInterpolation + def run_schtasks(task_action, options = {}) + cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" " + options.each_key do |option| + unless option == "TR" + cmd += "/#{option} " + cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == "" + end + end + # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value + if options["TR"] + cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE" + end + logger.trace("running: ") + logger.trace(" #{cmd}") + shell_out!(cmd, returns: [0]) + end + # rubocop:enable Style/StringLiteralsInInterpolation - def parse_day(str) - Date.strptime(str, "%m/%d/%Y") + def get_day(date) + Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase + end end - end end end diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb index 8b4743eecc..38f651d670 100644 --- a/lib/chef/resource/windows_task.rb +++ b/lib/chef/resource/windows_task.rb @@ -28,7 +28,7 @@ class Chef " scheduled task. Requires Windows Server 2008 or later due to API usage." introduced "13.0" - allowed_actions :create, :delete, :run, :end, :enable, :disable + allowed_actions :create, :delete, :run, :end, :enable, :disable, :change default_action :create property :task_name, String, regex: [/\A[^\/\:\*\?\<\>\|]+\z/], name_property: true @@ -49,7 +49,7 @@ class Chef :on_logon, :onstart, :on_idle, - :none], default: :hourly + :none] property :start_day, String property :start_time, String property :day, [String, Integer] @@ -57,36 +57,59 @@ class Chef property :idle_time, Integer property :random_delay, [String, Integer] property :execution_time_limit, [String, Integer], default: "PT72H" # 72 hours in ISO8601 duration format + property :minutes_duration, [String, Integer] + property :minutes_interval, [String, Integer] - attr_accessor :exists, :status, :enabled + attr_accessor :exists, :task + + SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze + VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * } + VALID_DAYS_OF_MONTH = ("1".."31").to_a << "last" << "lastday" + VALID_MONTHS = %w{JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC *} + VALID_WEEKS = %w{FIRST SECOND THIRD FOURTH LAST LASTDAY} def after_created if random_delay validate_random_delay(random_delay, frequency) - duration = sec_to_dur(random_delay) - random_delay(duration) + random_delay(sec_to_min(random_delay)) end if execution_time_limit - unless execution_time_limit == "PT72H" # don't double convert an ISO8601 format duration - raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(execution_time_limit) - duration = sec_to_dur(execution_time_limit) - execution_time_limit(duration) - end + execution_time_limit(259200) if execution_time_limit == "PT72H" + raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(execution_time_limit) + execution_time_limit(sec_to_min(execution_time_limit)) end + # Removed the default value :hourly for frequency in Chef 14 following code added for backward compatibility + chef_version = ::Chef::VERSION.split(".") + if chef_version[0].to_i > 13 || (chef_version[0].to_i >= 0) + validate_frequency(frequency) if action.include?(:create) || action.include?(:change) + else + frequency(:hourly) if frequency.nil? + end validate_start_time(start_time, frequency) validate_start_day(start_day, frequency) if start_day validate_user_and_password(user, password) validate_interactive_setting(interactive_enabled, password) - validate_create_frequency_modifier(frequency, frequency_modifier) - validate_create_day(day, frequency) if day + validate_create_frequency_modifier(frequency, frequency_modifier) if frequency_modifier + validate_create_day(day, frequency, frequency_modifier) if day validate_create_months(months, frequency) if months + validate_frequency_monthly(frequency_modifier, months, day) if frequency == :monthly validate_idle_time(idle_time, frequency) + idempotency_warning_for_frequency_weekly(day, start_day) if frequency == :weekly end private + ## Resource is not idempotent when day, start_day is not provided with frequency :weekly + ## we set start_day when not given by user as current date based on which we set the day property for current current date day is monday .. + ## we set the monday as the day so at next run when new_resource.day is nil and current_resource day is monday due to which udpate gets called + def idempotency_warning_for_frequency_weekly(day, start_day) + if start_day.nil? && day.nil? + logger.warn "To maintain idempotency for frequency :weekly provide start_day, start_time and day." + end + end + # Validate the passed value is numeric values only if it is a string def numeric_value_in_string?(val) return true if Integer(val) @@ -94,9 +117,35 @@ class Chef false end + def validate_frequency(frequency) + if frequency.nil? || !([:minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none].include?(frequency)) + raise ArgumentError, "Frequency needs to be provided. Valid frequencies are :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none." + end + end + + def validate_frequency_monthly(frequency_modifier, months, day) + # validates the frequency :monthly and raises error if frequency_modifier is first, second, thrid etc and day is not provided + if (frequency_modifier != 1) && (frequency_modifier_includes_days_of_weeks?(frequency_modifier)) && !(day) + raise ArgumentError, "Please select day on which you want to run the task e.g. 'Mon, Tue'. Multiple values must be seprated by comma." + end + + # frequency_modifer 2-12 is used to set every (n) months, so using :months propety with frequency_modifer is not valid since they both used to set months. + # Not checking value 1 here for frequecy_modifier since we are setting that as default value it won't break anything since preference is given to months property + if (frequency_modifier.to_i.between?(2, 12)) && !(months.nil?) + raise ArgumentError, "For frequency :monthly either use property months or frequency_modifier to set months." + end + end + + # returns true if frequency_modifer has values First, second, third, fourth, last, lastday + def frequency_modifier_includes_days_of_weeks?(frequency_modifier) + frequency_modifier = frequency_modifier.to_s.split(",") + frequency_modifier.map! { |value| value.strip.upcase } + (frequency_modifier - VALID_WEEKS).empty? + end + def validate_random_delay(random_delay, frequency) - if [:once, :on_logon, :onstart, :on_idle, :none].include? frequency - raise ArgumentError, "`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly" + if [:on_logon, :onstart, :on_idle, :none].include? frequency + raise ArgumentError, "`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly" end raise ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(random_delay) @@ -104,12 +153,14 @@ class Chef # @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~f def validate_start_day(start_day, frequency) - if [:once, :on_logon, :onstart, :on_idle, :none].include? frequency + if start_day && frequency == :none raise ArgumentError, "`start_day` property is not supported with frequency: #{frequency}" end # make sure the start_day is in MM/DD/YYYY format: http://rubular.com/r/cgjHemtWl5 - raise ArgumentError, "`start_day` property must be in the MM/DD/YYYY format." unless /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d$/ =~ start_day + if start_day + raise ArgumentError, "`start_day` property must be in the MM/DD/YYYY format." unless /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d$/ =~ start_day + end end # @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~ @@ -122,8 +173,6 @@ class Chef end end - SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze - def validate_user_and_password(user, password) if password_required?(user) && password.nil? raise ArgumentError, %q{Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: 'NT AUTHORITY\SYSTEM', 'SYSTEM', 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', 'USERS'} @@ -136,65 +185,79 @@ class Chef end def validate_interactive_setting(interactive_enabled, password) - if interactive_enabled && password.nil? - raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." - end + raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." if interactive_enabled && password.nil? end def validate_create_frequency_modifier(frequency, frequency_modifier) - # Currently is handled in create action 'frequency_modifier_allowed' line. Does not allow for frequency_modifier for once,onstart,onlogon,onidle,none - # Note that 'OnEvent' is not a supported frequency. - unless frequency.nil? || frequency_modifier.nil? - case frequency - when :minute - unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 1439 - raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :minute frequency are 1 - 1439." - end - when :hourly - unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 23 - raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :hourly frequency are 1 - 23." - end - when :daily - unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 365 - raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :daily frequency are 1 - 365." - end - when :weekly - unless frequency_modifier.to_i > 0 && frequency_modifier.to_i <= 52 - raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :weekly frequency are 1 - 52." - end - when :monthly - unless ("1".."12").to_a.push("FIRST", "SECOND", "THIRD", "FOURTH", "LAST", "LASTDAY").include?(frequency_modifier.to_s.upcase) - raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'." + if ([:on_logon, :onstart, :on_idle, :none].include?(frequency)) && ( frequency_modifier != 1) + raise ArgumentError, "frequency_modifier property not supported with frequency :#{frequency}" + end + + if frequency == :monthly + unless (1..12).cover?(frequency_modifier.to_i) || frequency_modifier_includes_days_of_weeks?(frequency_modifier) + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'." + end + else + unless frequency.nil? || frequency_modifier.nil? + frequency_modifier = frequency_modifier.to_i + min = 1 + max = case frequency + when :minute + 1439 + when :hourly + 23 + when :daily + 365 + when :weekly + 52 + else + min + end + unless frequency_modifier.between?(min, max) + raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :#{frequency} frequency are #{min} - #{max}." end end end end - def validate_create_day(day, frequency) - unless [:weekly, :monthly].include?(frequency) - raise "day property is only valid for tasks that run monthly or weekly" - end + def validate_create_day(day, frequency, frequency_modifier) + raise ArgumentError, "day property is only valid for tasks that run monthly or weekly" unless [:weekly, :monthly].include?(frequency) + + # This has been verified with schtask.exe https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday-- + # verified with earlier code if day "*" is given with frequency it raised exception Invalid value for /D option + raise ArgumentError, "day wild card (*) is only valid with frequency :weekly" if frequency == :monthly && day == "*" + if day.is_a?(String) && day.to_i.to_s != day days = day.split(",") - days.each do |d| - unless ["mon", "tue", "wed", "thu", "fri", "sat", "sun", "*"].include?(d.strip.downcase) - raise ArgumentError, "day property invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma." + if days_includes_days_of_months?(days) + # Following error will be raise if day is set as 1-31 and frequency is selected as :weekly since those values are valid with only frequency :monthly + raise ArgumentError, "day values 1-31 or last is only valid with frequency :monthly" if frequency == :weekly + else + days.map! { |day| day.to_s.strip.downcase } + unless (days - VALID_WEEK_DAYS).empty? + raise ArgumentError, "day property invalid. Only valid values are: #{VALID_WEEK_DAYS.map(&:upcase).join(', ')}. Multiple values must be separated by a comma." end end end end def validate_create_months(months, frequency) - raise ArgumentError, "months property is only valid for tasks that run monthly" unless frequency == :monthly - if months.is_a? String - months.split(",").each do |month| - unless ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", "*"].include?(month.strip.upcase) - raise ArgumentError, "months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma." - end + raise ArgumentError, "months property is only valid for tasks that run monthly" if frequency != :monthly + if months.is_a?(String) + months = months.split(",") + months.map! { |month| month.strip.upcase } + unless (months - VALID_MONTHS).empty? + raise ArgumentError, "months property invalid. Only valid values are: #{VALID_MONTHS.join(', ')}. Multiple values must be separated by a comma." end end end + # This method returns true if day has values from 1-31 which is a days of moths and used with frequency :monthly + def days_includes_days_of_months?(days) + days.map! { |day| day.to_s.strip.downcase } + (days - VALID_DAYS_OF_MONTH).empty? + end + def validate_idle_time(idle_time, frequency) if !idle_time.nil? && frequency != :on_idle raise ArgumentError, "idle_time property is only valid for tasks that run on_idle" @@ -216,6 +279,9 @@ class Chef ISO8601::Duration.new(seconds.to_i).to_s end + def sec_to_min(seconds) + seconds.to_i / 60 + end end end end diff --git a/spec/functional/resource/windows_task_spec.rb b/spec/functional/resource/windows_task_spec.rb index 2839b12288..70f3e6d92e 100644 --- a/spec/functional/resource/windows_task_spec.rb +++ b/spec/functional/resource/windows_task_spec.rb @@ -31,24 +31,29 @@ describe Chef::Resource::WindowsTask, :windows_only do describe "action :create" do after { delete_task } - context "when frequency and frequency_modifier are not passed" do + context "when frequency_modifier are not passed" do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this # Make sure MM/DD/YYYY is accepted new_resource.start_day "09/20/2017" + new_resource.frequency :hourly new_resource end it "creates a scheduled task to run every 1 hr starting on 09/20/2017" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:"Repeat:Every"]).to eq("1 Hour(s), 0 Minute(s)") - - # This test will not work across locales - expect(task_details[:StartDate]).to eq("9/20/2017") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq("chef-client") + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:start_year]).to eq("2017") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("20") + expect(trigger_details[:minutes_interval]).to eq(60) + expect(trigger_details[:trigger_type]).to eq(1) end it "does not converge the resource if it is already converged" do @@ -65,16 +70,20 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.run_level :highest new_resource.frequency :minute new_resource.frequency_modifier 15 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "creates a scheduled task that runs after every 15 minutes" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:"Repeat:Every"]).to eq("0 Hour(s), 15 Minute(s)") - expect(task_details[:run_level]).to eq("HighestAvailable") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:minutes_interval]).to eq(15) + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) end it "does not converge the resource if it is already converged" do @@ -82,6 +91,24 @@ describe Chef::Resource::WindowsTask, :windows_only do subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end + + it "updates a scheduled task when frequency_modifier updated to 20" do + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:minutes_interval]).to eq(15) + subject.frequency_modifier 20 + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + # #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:minutes_interval]).to eq(20) + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) + end end context "frequency :hourly" do @@ -91,16 +118,19 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.run_level :highest new_resource.frequency :hourly new_resource.frequency_modifier 3 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "creates a scheduled task that runs after every 3 hrs" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:"Repeat:Every"]).to eq("3 Hour(s), 0 Minute(s)") - expect(task_details[:run_level]).to eq("HighestAvailable") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:minutes_interval]).to eq(180) + expect(trigger_details[:trigger_type]).to eq(1) end it "does not converge the resource if it is already converged" do @@ -108,6 +138,23 @@ describe Chef::Resource::WindowsTask, :windows_only do subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end + + it "updates a scheduled task to run every 5 hrs when frequency modifer updated to 5" do + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:minutes_interval]).to eq(180) + # updating frequency modifer to 5 from 3 + subject.frequency_modifier 5 + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:minutes_interval]).to eq(300) + expect(trigger_details[:trigger_type]).to eq(1) + end end context "frequency :daily" do @@ -116,17 +163,20 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.command task_name new_resource.run_level :highest new_resource.frequency :daily + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "creates a scheduled task to run daily" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("Daily") - expect(task_details[:Days]).to eq("Every 1 day(s)") - expect(task_details[:run_level]).to eq("HighestAvailable") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(2) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days_interval]).to eq(1) end it "does not converge the resource if it is already converged" do @@ -136,127 +186,465 @@ describe Chef::Resource::WindowsTask, :windows_only do end end - context "frequency :monthly" do + describe "frequency :monthly" do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.run_level :highest new_resource.frequency :monthly - new_resource.frequency_modifier 2 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end - it "creates a scheduled task to every 2 months" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("Monthly") - expect(task_details[:Months]).to eq("FEB, APR, JUN, AUG, OCT, DEC") - expect(task_details[:run_level]).to eq("HighestAvailable") - end + context "with start_day and start_time" do + before do + subject.start_day "02/12/2018" + subject.start_time "05:15" + end - it "does not converge the resource if it is already converged" do - skip "This functionality needs to be handle" - subject.run_action(:create) - subject.run_action(:create) - expect(subject).not_to be_updated_by_last_action - end - end + it "if day property is not set creates a scheduled task to run monthly on first day of the month" do + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) + end - context "frequency :once" do - subject do - new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) - new_resource.command task_name - new_resource.run_level :highest - new_resource.frequency :once - new_resource + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on first, second and third day of the month" do + subject.day "1, 2, 3" + call_for_create_action + #loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(7) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on 1, 2, 3, 4, 8, 20, 21, 15, 28, 31 day of the month" do + subject.day "1, 2, 3, 4, 8, 20, 21, 15, 28, 31" + call_for_create_action + #loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1209548943) #TODO:: windows_task_provider.send(:days_of_month) + expect(trigger_details[:type][:months]).to eq(4095) #windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3, 4, 8, 20, 21, 15, 28, 31" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on Jan, Feb, Apr, Dec on 1st 2nd 3rd 4th 8th and 20th day of these months" do + subject.day "1, 2, 3, 4, 8, 20, 21, 30" + subject.months "Jan, Feb, May, Sep, Dec" + call_for_create_action + #loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(538443919) #TODO:windows_task_provider.send(:days_of_month) + expect(trigger_details[:type][:months]).to eq(2323) #windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3, 4, 8, 20, 21, 30" + subject.months "Jan, Feb, May, Sep, Dec" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly by giving day option with frequency_modifier" do + subject.frequency_modifier "First" + subject.day "Mon, Fri, Sun" + call_for_create_action + #loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(5) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(35) + expect(trigger_details[:type][:weeks_of_month]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) #windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "First" + subject.day "Mon, Fri, Sun" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end end - context "when start_time is not provided" do - it "raises argument error" do - expect { subject.run_action(:create) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + context "with frequency_modifier" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "raises argument error if frequency_modifier is 'first, second' and day is not provided." do + subject.frequency_modifier "first, second" + expect { subject.after_created }.to raise_error("Please select day on which you want to run the task e.g. 'Mon, Tue'. Multiple values must be seprated by comma.") + end + + it "raises argument error if months is passed along with frequency_modifier" do + subject.frequency_modifier 3 + subject.months "Jan, Mar" + expect { subject.after_created }.to raise_error("For frequency :monthly either use property months or frequency_modifier to set months.") + end + + it "not raises any Argument error if frequency_modifier set as 'first, second, third' and day is provided" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error(ArgumentError) + end + + it "not raises any Argument error if frequency_modifier 2 " do + subject.frequency_modifier 2 + subject.day "Mon, Sun" + expect { subject.after_created }.not_to raise_error(ArgumentError) + end + + it "raises argument error if frequency_modifier > 12" do + subject.frequency_modifier 13 + expect { subject.after_created }.to raise_error("frequency_modifier value 13 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + + it "raises argument error if frequency_modifier < 1" do + subject.frequency_modifier 0 + expect { subject.after_created }.to raise_error("frequency_modifier value 0 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + + it "creates scheduled task to run task monthly on Monday and Friday of first, second and thrid week of month" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(5) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:weeks_of_month]).to eq(7) + expect(trigger_details[:type][:days_of_week]).to eq(34) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task to run task monthly on every 6 months when frequency_modifier is 6 and to run on 1st and 2nd day of month" do + subject.frequency_modifier 6 + subject.day "1, 2" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(2080) + expect(trigger_details[:type][:days]).to eq(3) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier 6 + subject.day "1, 2" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action end end - context "when start_time is provided" do - it "creates the scheduled task to run once at 5pm" do - subject.start_time "17:00" + context "when day is set as last or lastday for frequency :monthly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "creates scheduled task to run monthly to run last day of the month" do + subject.day "last" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(0) + expect(trigger_details[:run_on_last_day_of_month]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.day "last" + subject.run_action(:create) subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("One Time Only") - expect(task_details[:StartTime]).to eq("5:00:00 PM") - expect(task_details[:run_level]).to eq("HighestAvailable") + expect(subject).not_to be_updated_by_last_action + end + + it "day property set as 'lastday' creates scheduled task to run monthly to run last day of the month" do + subject.day "lastday" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(0) + expect(trigger_details[:run_on_last_day_of_month]).to eq(true) end it "does not converge the resource if it is already converged" do - subject.start_time "17:00" + subject.day "lastday" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end end - end - context "frequency :none" do - subject do - new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) - new_resource.command task_name - new_resource.run_level :highest - new_resource.frequency :none - new_resource + context "when frequency_modifier is set as last for frequency :monthly" do + it "creates scheduled task to run monthly on last week of the month" do + subject.frequency_modifier "last" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(5) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days_of_week]).to eq(34) + expect(trigger_details[:run_on_last_week_of_month]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "last" + subject.day "Mon, Fri" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end end - it "creates the scheduled task to run on demand only" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) + context "when wild card (*) set as months" do + it "creates the scheduled task to run on 1st day of the all months" do + subject.months "*" + expect { subject.after_created }.not_to raise_error(ArgumentError) + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(1) + end - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("On demand only") - expect(task_details[:StartTime]).to eq("N/A") - expect(task_details[:StartDate]).to eq("N/A") - expect(task_details[:NextRunTime]).to eq("N/A") - expect(task_details[:none]).to eq(true) - expect(task_details[:run_level]).to eq("HighestAvailable") + it "does not converge the resource if it is already converged" do + subject.months "*" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end end - it "does not converge the resource if it is already converged" do - subject.run_action(:create) - subject.run_action(:create) - expect(subject).not_to be_updated_by_last_action + context "when wild card (*) set as day" do + it "raises argument error" do + subject.day "*" + expect { subject.after_created }.to raise_error("day wild card (*) is only valid with frequency :weekly") + end + end + + context "Pass either start day or start time by passing day compulsory or only pass frequency_modifier" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "creates a scheduled task to run monthly on second day of the month" do + subject.day "2" + subject.start_day "03/07/2018" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(2) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "2" + subject.start_day "03/07/2018" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on first, second and third day of the month" do + subject.day "1,2,3" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(7) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "1,2,3" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on each wednesday of the month" do + subject.frequency_modifier "1" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) #windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "2" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on each wednesday of the month" do + subject.frequency_modifier "2" + subject.months = nil + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + #loading current resource + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(2730) #windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "2" + subject.months = nil + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end end end - context "frequency :onstart" do + ## ToDO: Add functional specs to handle frequency monthly with frequency modifier set as 1-12 + context "frequency :once" do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.run_level :highest - new_resource.frequency :onstart + new_resource.frequency :once + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end - it "creates the scheduled task to run at system start up" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("At system start up") - expect(task_details[:StartTime]).to eq("N/A") - expect(task_details[:StartDate]).to eq("N/A") - expect(task_details[:NextRunTime]).to eq("N/A") - expect(task_details[:onstart]).to eq(true) - expect(task_details[:run_level]).to eq("HighestAvailable") + context "when start_time is not provided" do + it "raises argument error" do + expect { subject.after_created }.to raise_error("`start_time` needs to be provided with `frequency :once`") + end end - it "does not converge the resource if it is already converged" do - subject.run_action(:create) - subject.run_action(:create) - expect(subject).not_to be_updated_by_last_action + context "when start_time is provided" do + it "creates the scheduled task to run once at 5pm" do + subject.start_time "17:00" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect("#{trigger_details[:start_hour]}:#{trigger_details[:start_minute]}" ).to eq(subject.start_time) + end + + it "does not converge the resource if it is already converged" do + subject.start_time "17:00" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end end end @@ -266,60 +654,192 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.command task_name new_resource.run_level :highest new_resource.frequency :weekly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "creates the scheduled task to run weekly" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("Weekly") - expect(task_details[:Months]).to eq("Every 1 week(s)") - expect(task_details[:run_level]).to eq("HighestAvailable") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) end it "does not converge the resource if it is already converged" do - skip "This functionality needs to be handle" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end + context "when wild card (*) is set as day" do + it "creates hte scheduled task for all days of week" do + subject.day "*" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(127) + end + + it "does not converge the resource if it is already converged" do + subject.day "*" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + context "when days are provided" do it "creates the scheduled task to run on particular days" do subject.day "Mon, Fri" subject.frequency_modifier 2 + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(2) + expect(trigger_details[:type][:days_of_week]).to eq(34) + end + + it "updates the scheduled task to run on if frequency_modifier is updated" do + subject.day "sun" + subject.frequency_modifier 2 + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:type][:weeks_interval]).to eq(2) + expect(trigger_details[:type][:days_of_week]).to eq(1) + subject.day "Mon, Sun" + subject.frequency_modifier 3 + # call for update subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:Days]).to eq("MON, FRI") - expect(task_details[:ScheduleType]).to eq("Weekly") - expect(task_details[:Months]).to eq("Every 2 week(s)") - expect(task_details[:run_level]).to eq("HighestAvailable") + expect(subject).to be_updated_by_last_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(3) + expect(trigger_details[:type][:days_of_week]).to eq(3) end it "does not converge the resource if it is already converged" do subject.day "Mon, Fri" - subject.frequency_modifier 2 + subject.frequency_modifier 3 subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end end + context "when day property set as last" do + it "raises argument error" do + subject.day "last" + expect { subject.after_created }.to raise_error("day values 1-31 or last is only valid with frequency :monthly") + end + end + context "when invalid day is passed" do it "raises error" do subject.day "abc" - expect { subject.run_action(:create) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + expect { subject.after_created }.to raise_error("day property invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN, *. Multiple values must be separated by a comma.") end end context "when months are passed" do it "raises error that months are supported only when frequency=:monthly" do subject.months "Jan" - expect { subject.run_action(:create) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + expect { subject.after_created }.to raise_error("months property is only valid for tasks that run monthly") + end + end + + context "when start_day is not set" do + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates the day if start_day is not provided and user updates day property" do + skip "Unable to run this test case since start_day is current system date which can be different each time so can't verify the dynamic values" + subject.run_action(:create) + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:type][:days_of_week]).to eq(8) + subject.day "Sat" + subject.run_action(:create) + # #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(64) + end + end + end + + context "frequency :onstart" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :onstart + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "creates the scheduled task to run at system start up" do + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(8) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(8) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") end end end @@ -330,16 +850,19 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.command task_name new_resource.run_level :highest new_resource.frequency :on_logon + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "creates the scheduled task to on logon" do - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("At logon time") - expect(task_details[:run_level]).to eq("HighestAvailable") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(9) end it "does not converge the resource if it is already converged" do @@ -347,6 +870,25 @@ describe Chef::Resource::WindowsTask, :windows_only do subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(9) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") + end + end end context "frequency :on_idle" do @@ -355,25 +897,29 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.command task_name new_resource.run_level :highest new_resource.frequency :on_idle + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end context "when idle_time is not passed" do it "raises error" do - expect { subject.run_action(:create) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + expect { subject.after_created }.to raise_error("idle_time value should be set for :on_idle frequency.") end end context "when idle_time is passed" do it "creates the scheduled task to run when system is idle" do subject.idle_time 20 - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:ScheduleType]).to eq("At idle time") - expect(task_details[:run_level]).to eq("HighestAvailable") - expect(task_details[:idle_time]).to eq("PT20M") + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(6) + expect(current_resource.task.settings[:idle_settings][:idle_duration]).to eq("PT20M") + expect(current_resource.task.settings[:run_only_if_idle]).to eq(true) end it "does not converge the resource if it is already converged" do @@ -383,6 +929,26 @@ describe Chef::Resource::WindowsTask, :windows_only do expect(subject).not_to be_updated_by_last_action end end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.idle_time 20 + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(trigger_details[:trigger_type]).to eq(6) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") + end + end end context "when random_delay is passed" do @@ -390,24 +956,27 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "sets the random_delay for frequency :minute" do subject.frequency :minute - subject.random_delay "PT20M" - subject.run_action(:create) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:TaskName]).to eq("\\chef-client") - expect(task_details[:ScheduleType]).to eq("One Time Only, Minute") - expect(task_details[:TaskToRun]).to eq("chef-client") - expect(task_details[:run_level]).to eq("HighestAvailable") - expect(task_details[:random_delay]).to eq("PT20M") + subject.random_delay "20" + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(1) + expect(trigger_details[:random_minutes_interval]).to eq(20) end it "does not converge the resource if it is already converged" do subject.frequency :minute - subject.random_delay "PT20M" + subject.random_delay "20" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action @@ -421,8 +990,36 @@ describe Chef::Resource::WindowsTask, :windows_only do it "raises error if random_delay is passed with frequency on_idle" do subject.frequency :on_idle - subject.random_delay "PT20M" - expect { subject.after_created }.to raise_error("`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly") + subject.random_delay "20" + expect { subject.after_created }.to raise_error("`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly") + end + end + + context "frequency :none" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :none + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "creates the scheduled task to run on demand only" do + call_for_create_action + #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + + expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(current_resource.task.trigger_count).to eq(0) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action end end end @@ -436,6 +1033,7 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.run_level :highest new_resource.frequency :once new_resource.start_time "17:00" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end @@ -447,7 +1045,6 @@ describe Chef::Resource::WindowsTask, :windows_only do end it "create task by adding frequency_modifier as 5" do - skip "This functionality needs to be handle" subject.frequency_modifier 5 subject.run_action(:create) subject.run_action(:create) @@ -460,6 +1057,7 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource.frequency :none new_resource end @@ -472,7 +1070,6 @@ describe Chef::Resource::WindowsTask, :windows_only do end it "create task by adding frequency_modifier as 5" do - skip "This functionality needs to be handle" subject.frequency_modifier 5 subject.run_action(:create) subject.run_action(:create) @@ -486,11 +1083,11 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.command task_name new_resource.run_level :highest new_resource.frequency :weekly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "create task by adding start_day" do - skip "This functionality needs to be handle" subject.start_day "12/28/2018" subject.run_action(:create) subject.run_action(:create) @@ -498,7 +1095,6 @@ describe Chef::Resource::WindowsTask, :windows_only do end it "create task by adding frequency_modifier and random_delay" do - skip "This functionality needs to be handle" subject.frequency_modifier 3 subject.random_delay "60" subject.run_action(:create) @@ -514,6 +1110,7 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.run_level :highest new_resource.frequency :once new_resource.start_time "17:00" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end @@ -525,7 +1122,6 @@ describe Chef::Resource::WindowsTask, :windows_only do end it "create task by adding frequency_modifier as 5" do - skip "This functionality needs to be handle" subject.frequency_modifier 5 subject.run_action(:create) subject.run_action(:create) @@ -541,11 +1137,11 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.frequency :hourly new_resource.frequency_modifier 5 new_resource.random_delay "2400" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "create task by adding frequency_modifier and random_delay" do - skip "This functionality needs to be handle" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action @@ -560,11 +1156,11 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.frequency :daily new_resource.frequency_modifier 2 new_resource.random_delay "2400" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "create task by adding frequency_modifier and random_delay" do - skip "This functionality needs to be handle" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action @@ -576,6 +1172,7 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.frequency :on_logon + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end @@ -586,7 +1183,6 @@ describe Chef::Resource::WindowsTask, :windows_only do end it "create task by adding frequency_modifier as 5" do - skip "This functionality needs to be handle" subject.frequency_modifier 5 subject.run_action(:create) subject.run_action(:create) @@ -601,11 +1197,11 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource.run_level :highest new_resource.frequency :onstart new_resource.frequency_modifier 20 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end it "create task by adding frequency_modifier as 20" do - skip "This functionality needs to be handle" subject.run_action(:create) subject.run_action(:create) expect(subject).not_to be_updated_by_last_action @@ -618,14 +1214,15 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this new_resource end context "when start_day is passed with frequency :onstart" do - it "raises error" do + it "not raises error" do subject.frequency :onstart subject.start_day "09/20/2017" - expect { subject.after_created }.to raise_error("`start_day` property is not supported with frequency: onstart") + expect { subject.after_created }.not_to raise_error end end @@ -653,11 +1250,51 @@ describe Chef::Resource::WindowsTask, :windows_only do end end + context "when frequency_modifier > 23 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 24 + subject.frequency :hourly + expect { subject.after_created }.to raise_error("frequency_modifier value 24 is invalid. Valid values for :hourly frequency are 1 - 23.") + end + end + + context "when frequency_modifier > 23 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 366 + subject.frequency :daily + expect { subject.after_created }.to raise_error("frequency_modifier value 366 is invalid. Valid values for :daily frequency are 1 - 365.") + end + end + + context "when frequency_modifier > 52 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 53 + subject.frequency :weekly + expect { subject.after_created }.to raise_error("frequency_modifier value 53 is invalid. Valid values for :weekly frequency are 1 - 52.") + end + end + + context "when invalid frequency_modifier is passed for :monthly frequency" do + it "raises error" do + subject.frequency :monthly + subject.frequency_modifier "13" + expect { subject.after_created }.to raise_error("frequency_modifier value 13 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + end + + context "when invalid frequency_modifier is passed for :monthly frequency" do + it "raises error" do + subject.frequency :monthly + subject.frequency_modifier "xyz" + expect { subject.after_created }.to raise_error("frequency_modifier value xyz is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + end + context "when invalid months are passed" do it "raises error" do subject.months "xyz" subject.frequency :monthly - expect { subject.after_created }.to raise_error("months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma.") + expect { subject.after_created }.to raise_error("months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, *. Multiple values must be separated by a comma.") end end @@ -682,14 +1319,16 @@ describe Chef::Resource::WindowsTask, :windows_only do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource.frequency :hourly new_resource end - it "deletes the task if it exists" do + it "does not converge the resource if it is already converged" do subject.run_action(:create) - delete_task - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details).to eq(false) + subject.run_action(:delete) + subject.run_action(:delete) + expect(subject).not_to be_updated_by_last_action end it "does not converge the resource if it is already converged" do @@ -707,15 +1346,16 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command "dir" new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly new_resource end it "runs the existing task" do - skip "Task status is returned as Ready instead of Running randomly" subject.run_action(:create) subject.run_action(:run) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:Status]).to eq("Running") + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("queued").or eq("running") # queued or can be running end end @@ -726,16 +1366,16 @@ describe Chef::Resource::WindowsTask, :windows_only do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command "dir" new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since new_resource end it "ends the running task" do subject.run_action(:create) subject.run_action(:run) - task_details = windows_task_provider.send(:load_task_hash, task_name) subject.run_action(:end) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:Status]).to eq("Ready") + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("queued").or eq("ready") #queued or can be ready end end @@ -745,17 +1385,19 @@ describe Chef::Resource::WindowsTask, :windows_only do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly new_resource end it "enables the disabled task" do subject.run_action(:create) subject.run_action(:disable) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:ScheduledTaskState]).to eq("Disabled") + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("not scheduled") subject.run_action(:enable) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:ScheduledTaskState]).to eq("Enabled") + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("ready") end end @@ -765,14 +1407,32 @@ describe Chef::Resource::WindowsTask, :windows_only do subject do new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly new_resource end it "disables the task" do subject.run_action(:create) subject.run_action(:disable) - task_details = windows_task_provider.send(:load_task_hash, task_name) - expect(task_details[:ScheduledTaskState]).to eq("Disabled") + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("not scheduled") + end + end + + describe "action :change" do + after { delete_task } + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly + new_resource + end + + it "call action_create since change action is alias for create" do + subject.run_action(:change) + expect(subject).to be_updated_by_last_action end end @@ -780,4 +1440,15 @@ describe Chef::Resource::WindowsTask, :windows_only do task_to_delete = Chef::Resource::WindowsTask.new(task_name, run_context) task_to_delete.run_action(:delete) end + + def call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(false) + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + end + + def call_for_load_current_resource + windows_task_provider.send(:load_current_resource) + end end diff --git a/spec/unit/provider/windows_task_spec.rb b/spec/unit/provider/windows_task_spec.rb index a71ce92f56..603c9d1342 100644 --- a/spec/unit/provider/windows_task_spec.rb +++ b/spec/unit/provider/windows_task_spec.rb @@ -18,8 +18,9 @@ require "spec_helper" -describe Chef::Provider::WindowsTask do +describe Chef::Provider::WindowsTask, :windows_only do let(:new_resource) { Chef::Resource::WindowsTask.new("sample_task") } + let(:current_resource) { Chef::Resource::WindowsTask.new() } let(:provider) do node = Chef::Node.new @@ -28,690 +29,392 @@ describe Chef::Provider::WindowsTask do Chef::Provider::WindowsTask.new(new_resource, run_context) end - let(:task_hash) do - { - :"" => "", - :Folder => "\\", - :HostName => "NIMISHA-PC", - :TaskName => "\\sample_task", - :NextRunTime => "3/30/2017 2:42:00 PM", - :Status => "Ready", - :LogonMode => "Interactive/Background", - :LastRunTime => "3/30/2017 2:27:00 PM", - :LastResult => "1", - :Author => "Administrator", - :TaskToRun => "chef-client -L C:\\tmp\\", - :StartIn => "N/A", - :Comment => "N/A", - :ScheduledTaskState => "Enabled", - :IdleTime => "Disabled", - :PowerManagement => "Stop On Battery Mode, No Start On Batteries", - :RunAsUser => "SYSTEM", - :DeleteTaskIfNotRescheduled => "Enabled", - :StopTaskIfRunsXHoursandXMins => "72:00:00", - :Schedule => "Scheduling data is not available in this format.", - :ScheduleType => "One Time Only, Minute", - :StartTime => "1:12:00 PM", - :StartDate => "3/30/2017", - :EndDate => "N/A", - :Days => "N/A", - :Months => "N/A", - :"Repeat:Every" => "0 Hour(s), 15 Minute(s)", - :"Repeat:Until:Time" => "None", - :"Repeat:Until:Duration" => "Disabled", - :"Repeat:StopIfStillRunning" => "Disabled", - :run_level => "HighestAvailable", - :repetition_interval => "PT15M", - :execution_time_limit => "PT72H", - } - end - - let(:task_xml) do - "<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\r\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\r\r\n <RegistrationInfo>\r\r\n <Date>2017-03-31T15:34:44</Date>\r\r\n <Author>Administrator</Author>\r\r\n </RegistrationInfo>\r\r\n<Triggers>\r\r\n <TimeTrigger>\r\r\n <Repetition>\r\r\n <Interval>PT15M</Interval>\r\r\n <StopAtDurationEnd>false</StopAtDurationEnd>\r\r\n </Repetition>\r\r\n <StartBoundary>2017-03-31T15:34:00</StartBoundary>\r\r\n <Enabled>true</Enabled>\r\r\n </TimeTrigger>\r\r\n </Triggers>\r\r\n <Principals>\r\r\n <Principal id=\"Author\">\r\r\n <RunLevel>HighestAvailable</RunLevel>\r\r\n <UserId>S-1-5-18</UserId>\r\r\n </Principal>\r\r\n </Principals>\r\r\n <Settings>\r\r\n <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\r\r\n <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>\r\r\n <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>\r\r\n <AllowHardTerminate>true</AllowHardTerminate>\r\r\n <StartWhenAvailable>false</StartWhenAvailable>\r\r\n <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\r\r\n <IdleSettings>\r\r\n <Duration>PT10M</Duration>\r\r\n<WaitTimeout>PT1H</WaitTimeout>\r\r\n <StopOnIdleEnd>true</StopOnIdleEnd>\r\r\n <RestartOnIdle>false</RestartOnIdle>\r\r\n </IdleSettings>\r\r\n <AllowStartOnDemand>true</AllowStartOnDemand>\r\r\n <Enabled>true</Enabled>\r\r\n <Hidden>false</Hidden>\r\r\n<RunOnlyIfIdle>false</RunOnlyIfIdle>\r\r\n <WakeToRun>false</WakeToRun>\r\r\n <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\r\r\n<Priority>7</Priority>\r\r\n </Settings>\r\r\n <Actions Context=\"Author\">\r\r\n <Exec>\r\r\n <Command>chef-client</Command>\r\r\n </Exec>\r\r\n </Actions>\r\r\n</Task>" - end - describe "#load_current_resource" do it "returns a current_resource" do - allow(provider).to receive(:load_task_hash) expect(provider.load_current_resource).to be_kind_of(Chef::Resource::WindowsTask) end + end - context "if the given task name already exists" do - before do - allow(provider).to receive(:load_task_hash).and_return({ :TaskName => "\\sample_task" }) - end - - it "calls set_current_resource" do - expect(provider).to receive(:set_current_resource) - provider.load_current_resource - end + describe "#set_start_day_and_time" do + it "sets the curret date and time start_day and start_time if nothing is provided by user" do + new_resource.start_day = nil + new_resource.start_time = nil + provider.send(:set_start_day_and_time) + expect(new_resource.start_day).not_to be_nil + expect(new_resource.start_time).not_to be_nil end - it "sets the attributes of current_resource" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - current_resource = provider.load_current_resource - expect(current_resource.exists).to be(true) - expect(current_resource.command).to eq("chef-client -L C:\\tmp\\") - expect(current_resource.user).to eq("SYSTEM") - expect(current_resource.run_level).to eq(:highest) - expect(current_resource.frequency).to eq(:minute) - expect(current_resource.frequency_modifier).to eq(15) - expect(current_resource.execution_time_limit).to eq("PT72H") - expect(current_resource.enabled).to be(true) + it "does not set start_day and start_time if given by user" do + new_resource.start_day = "12/02/2017" + new_resource.start_time = "17:30" + provider.send(:set_start_day_and_time) + expect(new_resource.start_day).to eq("12/02/2017") + expect(new_resource.start_time).to eq("17:30") end end - describe "#action_create" do - it "doesn't create the same task if it's already existing" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - allow(provider).to receive(:task_need_update?).and_return(false) - provider.run_action(:create) - expect(new_resource).not_to be_updated_by_last_action - end - - it "sets the start_time in 24hr format while updating an existing task" do - # task_hash has start_time = "1:12:00 PM" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:convert_system_date_to_mm_dd_yyyy).and_return("03/30/2017") - allow(provider).to receive(:run_schtasks) - provider.run_action(:create) - # start_time gets set in 24hr format for new_resource - expect(new_resource.start_time).to eq("13:12") - expect(new_resource).to be_updated_by_last_action - end - - it "sets the start_day in mm/dd/yyyy format while updating an existing task" do - # start_day in yyyy-MM-dd format - task_hash[:StartDate] = "2017-03-30" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - current_resource = provider.load_current_resource - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:convert_system_date_format_to_ruby_date_format).and_return("%Y-%m-%d") - allow(provider).to receive(:run_schtasks) - provider.run_action(:create) - # start_day gets set in mm/dd/yyyy format for new_resource - expect(new_resource.start_day).to eq("03/30/2017") - expect(new_resource).to be_updated_by_last_action - end - - context "when start_day and start_time are N/A for frequency :on_logon" do - it "doesn't update the start_day and start_time of new_resource" do - task_hash[:on_logon] = true - task_hash[:StartDate] = "N/A" - task_hash[:StartTime] = "N/A" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - current_resource = provider.load_current_resource - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:run_schtasks) - expect(provider).not_to receive(:convert_system_date_to_mm_dd_yyyy) - expect(DateTime).not_to receive(:parse) - expect(new_resource.start_day).to eq(nil) - expect(new_resource.start_time).to eq(nil) - end - end - - context "when task is not existing" do - before do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - end - - it "creates the task if it's not already existing" do - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:basic_validation).and_return(true) - expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) - provider.run_action(:create) - expect(new_resource).to be_updated_by_last_action - end - - it "updates the task XML if random_delay is provided" do - new_resource.random_delay "20" - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:basic_validation).and_return(true) - expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) - expect(provider).to receive(:update_task_xml) - provider.run_action(:create) - expect(new_resource).to be_updated_by_last_action - end - - it "updates the task XML if execution_time_limit is provided" do - new_resource.execution_time_limit "20" - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:basic_validation).and_return(true) - expect(provider).to receive(:run_schtasks).with("CREATE", { "F" => "", "SC" => :hourly, "MO" => 1, "TR" => nil, "RU" => "SYSTEM" }) - expect(provider).to receive(:update_task_xml) - provider.run_action(:create) - expect(new_resource).to be_updated_by_last_action - end - - it "updates the task XML if frequency is set as `:none`" do - new_resource.frequency :none - new_resource.random_delay "" - allow(provider).to receive(:task_need_update?).and_return(true) - allow(provider).to receive(:basic_validation).and_return(true) - allow(provider).to receive(:run_schtasks).and_return("CREATE", { "F" => "", "SC" => :once, "ST" => "00:00", "SD" => "12/12/2012", "TR" => nil, "RU" => "SYSTEM" }) - expect(provider).to receive(:update_task_xml) - provider.run_action(:create) - expect(new_resource).to be_updated_by_last_action - end + describe "#trigger" do + it "returns the trigger values in hash format" do + new_resource.start_day "12/02/2017" + new_resource.start_time "17:30" + new_resource.frequency :minute + new_resource.frequency_modifier 15 + new_resource.random_delay 60 + result = { + :start_year => 2017, + :start_month => 12, + :start_day => 2, + :start_hour => 17, + :start_minute => 30, + :end_month => 0, + :end_day => 0, + :end_year => 0, + :trigger_type => 1, + :type => { :once => nil }, + :random_minutes_interval => 60, + :minutes_interval => 15, + :run_on_last_day_of_month => false, + :run_on_last_week_of_month => false + + } + expect(provider.send(:trigger)).to eq(result) end end - describe "#action_run" do - it "does nothing if the task doesn't exist" do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - provider.run_action(:run) - expect(new_resource).not_to be_updated_by_last_action - end - - context "when the task exists" do - it "does nothing if the task is already running" do - task_hash[:Status] = "Running" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - provider.run_action(:run) - expect(new_resource).not_to be_updated_by_last_action - end - - it "runs the task" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - expect(provider).to receive(:run_schtasks).with("RUN") - provider.run_action(:run) - expect(new_resource).to be_updated_by_last_action - end + describe "#convert_hours_in_minutes" do + it "converts given hours in minutes" do + expect(provider.send(:convert_hours_in_minutes, 5)).to eq(300) end end - describe "#action_delete" do - it "deletes the task if it exists" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - expect(provider).to receive(:run_schtasks).with("DELETE", { "F" => "" }) - provider.run_action(:delete) - expect(new_resource).to be_updated_by_last_action + describe "#trigger_type" do + it "returns 1 if frequency :once" do + new_resource.frequency :once + expect(provider.send(:trigger_type)).to eq(1) end - it "does nothing if the task doesn't exist" do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - provider.run_action(:delete) - expect(new_resource).not_to be_updated_by_last_action + it "returns 2 if frequency :daily" do + new_resource.frequency :daily + expect(provider.send(:trigger_type)).to eq(2) end - end - describe "#action_end" do - it "does nothing if the task doesn't exist" do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - provider.run_action(:end) - expect(new_resource).not_to be_updated_by_last_action - end - - context "when the task exists" do - it "does nothing if the task is not running" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - provider.run_action(:end) - expect(new_resource).not_to be_updated_by_last_action - end - - it "ends the task if it's running" do - task_hash[:Status] = "Running" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - expect(provider).to receive(:run_schtasks).with("END") - provider.run_action(:end) - expect(new_resource).to be_updated_by_last_action - end + it "returns 3 if frequency :weekly" do + new_resource.frequency :weekly + expect(provider.send(:trigger_type)).to eq(3) end - end - describe "#action_enable" do - it "raises error if the task doesn't exist" do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - expect { provider.run_action(:enable) }.to raise_error(Errno::ENOENT) - end - - context "when the task exists" do - it "does nothing if the task is already enabled" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - provider.run_action(:enable) - expect(new_resource).not_to be_updated_by_last_action - end - - it "enables the task if it exists" do - task_hash[:ScheduledTaskState] = "Disabled" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - expect(provider).to receive(:run_schtasks).with("CHANGE", { "ENABLE" => "" }) - provider.run_action(:enable) - expect(new_resource).to be_updated_by_last_action - end + it "returns 4 if frequency :monthly" do + new_resource.frequency :monthly + expect(provider.send(:trigger_type)).to eq(4) + end + + it "returns 5 if frequency :monthly and frequency_modifier is 'first, second'" do + new_resource.frequency :monthly + new_resource.frequency_modifier "first, second" + expect(provider.send(:trigger_type)).to eq(5) end - end - describe "#action_disable" do - it "does nothing if the task doesn't exist" do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - provider.run_action(:disable) - expect(new_resource).not_to be_updated_by_last_action - end - - context "when the task exists" do - it "disables the task if it's enabled" do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - expect(provider).to receive(:run_schtasks).with("CHANGE", { "DISABLE" => "" }) - provider.run_action(:disable) - expect(new_resource).to be_updated_by_last_action - end - - it "does nothing if the task is already disabled" do - task_hash[:ScheduledTaskState] = "Disabled" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - provider.run_action(:disable) - expect(new_resource).not_to be_updated_by_last_action - end + it "returns 6 if frequency :on_idle" do + new_resource.frequency :on_idle + expect(provider.send(:trigger_type)).to eq(6) end - end - describe "#run_schtasks" do - before do - @task_action = "CREATE" - @options = { "F" => "", "SC" => :minute, "MO" => 15, "TR" => "chef-client", "RU" => "SYSTEM", "RL" => "HIGHEST" } - @cmd = "schtasks /CREATE /TN \"sample_task\" /F /SC \"minute\" /MO \"15\" /RU \"SYSTEM\" /RL \"HIGHEST\" /TR \"chef-client \" " + it "returns 8 if frequency :onstart" do + new_resource.frequency :onstart + expect(provider.send(:trigger_type)).to eq(8) end - it "forms the command properly from the given options" do - expect(provider).to receive(:shell_out!).with(@cmd, { :returns => [0] }) - provider.send(:run_schtasks, @task_action, @options) + it "returns 9 if frequency :on_logon" do + new_resource.frequency :on_logon + expect(provider.send(:trigger_type)).to eq(9) end end - describe "#basic_validation" do - context "when command doesn't exist" do - it "raise error" do - new_resource.command "" - expect { provider.send(:basic_validation) }.to raise_error(Chef::Exceptions::ValidationFailed) - end + describe "#type" do + it "returns type hash when frequency :once" do + new_resource.frequency :once + new_resource.frequency_modifier 2 + result = provider.send(:type) + expect(result).to include(:once) + expect(result).to eq({ :once => nil }) end - context "when task_name doesn't exist" do - let(:new_resource) { Chef::Resource::WindowsTask.new("") } - it "raise error" do - expect { provider.send(:basic_validation) }.to raise_error(Chef::Exceptions::ValidationFailed) - end + it "returns type hash when frequency :daily" do + new_resource.frequency :daily + new_resource.frequency_modifier 2 + result = provider.send(:type) + expect(result).to include(:days_interval) + expect(result).to eq({ days_interval: 2 }) end - context "when task_name and command exists" do - it "returns true" do - new_resource.command "cd ~/" - expect(provider.send(:basic_validation)).to be(true) - end + it "returns type hash when frequency :weekly" do + new_resource.start_day "01/02/2018" + new_resource.frequency :weekly + new_resource.frequency_modifier 2 + result = provider.send(:type) + expect(result).to include(:weeks_interval) + expect(result).to include(:days_of_week) + expect(result).to eq({ weeks_interval: 2, days_of_week: 4 }) end - end - describe "#task_need_update?" do - context "when task doesn't exist" do - before do - allow(provider).to receive(:load_task_hash) - provider.load_current_resource - end - - it "returns true" do - new_resource.command "chef-client" - expect(provider.send(:task_need_update?)).to be(true) - end - end - - context "when the task exists" do - before do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - allow(provider).to receive(:get_system_short_date_format).and_return("MM/dd/yyyy") - provider.load_current_resource - - new_resource.command "chef-client -L C:\\tmp\\" - new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" - new_resource.execution_time_limit "PT72H" - new_resource.start_day "03/30/2017" - new_resource.start_time "13:12" - end - - context "when no attributes are modified" do - it "returns false" do - expect(provider.send(:task_need_update?)).to be(false) - end - end - - context "when frequency_modifier is updated" do - it "returns true" do - new_resource.frequency_modifier 25 - expect(provider.send(:task_need_update?)).to be(true) - end - end - - context "when months are updated" do - it "returns true" do - new_resource.months "JAN" - expect(provider.send(:task_need_update?)).to be(true) - end - end - - context "when start_day is updated" do - it "returns true" do - new_resource.start_day "01/01/2000" - expect(provider.send(:task_need_update?)).to be(true) - end - end - - context "when start_time updated" do - it "returns true" do - new_resource.start_time "01:01" - expect(provider.send(:task_need_update?)).to be(true) - end - end - - context "when command updated" do - it "return true" do - new_resource.command "chef-client" - expect(provider.send(:task_need_update?)).to be(true) - end - end + it "returns type hash when frequency :monthly" do + new_resource.frequency :monthly + result = provider.send(:type) + expect(result).to include(:months) + expect(result).to include(:days) + expect(result).to eq({ months: 4095, days: 1 }) end - end - describe "#start_day_updated?" do - before do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - allow(provider).to receive(:get_system_short_date_format).and_return("MM/dd/yyyy") - provider.load_current_resource + it "returns type hash when frequency :monthly with frequency_modifier 'first, second, third'" do + new_resource.start_day "01/02/2018" + new_resource.frequency :monthly + new_resource.frequency_modifier "First, Second, third" + result = provider.send(:type) + expect(result).to include(:months) + expect(result).to include(:days_of_week) + expect(result).to include(:weeks_of_month) + expect(result).to eq({ months: 4095, days_of_week: 4, weeks_of_month: 7 }) + end - new_resource.command "chef-client" - new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" - new_resource.execution_time_limit "PT72H" - new_resource.start_day "03/30/2017" - new_resource.start_time "13:12" + it "returns type hash when frequency :on_idle" do + new_resource.frequency :on_idle + result = provider.send(:type) + expect(result).to eq(nil) end - context "when start_day not changed" do - it "returns false" do - expect(provider.send(:start_day_updated?)).to be(false) - end + + it "returns type hash when frequency :onstart" do + new_resource.frequency :onstart + result = provider.send(:type) + expect(result).to eq(nil) end - context "when start_day changed" do - it "returns true" do - new_resource.start_day "01/01/2000" - expect(provider.send(:start_day_updated?)).to be(true) - end + it "returns type hash when frequency :on_logon" do + new_resource.frequency :on_logon + result = provider.send(:type) + expect(result).to eq(nil) end end - describe "#start_time_updated?" do - before do - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource + describe "#weeks_of_month" do + it "returns the binary value 1 if frequency_modifier is set as 'first'" do + new_resource.frequency_modifier "first" + expect(provider.send(:weeks_of_month)).to eq(1) + end - new_resource.command "chef-client" - new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" - new_resource.execution_time_limit "PT72H" - new_resource.start_day "3/30/2017" - new_resource.start_time "13:12" + it "returns the binary value 2 if frequency_modifier is set as 'second'" do + new_resource.frequency_modifier "second" + expect(provider.send(:weeks_of_month)).to eq(2) end - context "when start_time not changed" do - it "returns false" do - expect(provider.send(:start_time_updated?)).to be(false) - end + + it "returns the binary value 4 if frequency_modifier is set as 'third'" do + new_resource.frequency_modifier "third" + expect(provider.send(:weeks_of_month)).to eq(4) end - context "when start_time changed" do - it "returns true" do - new_resource.start_time "01:01" - expect(provider.send(:start_time_updated?)).to be(true) - end + it "returns the binary value 8 if frequency_modifier is set as 'fourth'" do + new_resource.frequency_modifier "fourth" + expect(provider.send(:weeks_of_month)).to eq(8) end - end - describe "#convert_user_date_to_system_date" do - it "when current resource start date is '05/30/2017' then returns '30/05/2017'" do - allow(provider).to receive(:get_system_short_date_format).and_return("dd/MM/yyyy") - expect(provider.send(:convert_user_date_to_system_date, "05/30/2017")).to eq("30/05/2017") + it "returns the binary value 16 if frequency_modifier is set as 'last'" do + new_resource.frequency_modifier "last" + expect(provider.send(:weeks_of_month)).to eq(nil) end end - describe "#convert_system_date_format_to_ruby_date_format" do - context "when system date format 'dd-MMM-yy'" do - it "returns '%d-%b-%y'" do - allow(provider).to receive(:get_system_short_date_format).and_return("dd-MMM-yy") - expect(provider.send(:convert_system_date_format_to_ruby_date_format)).to eq("%d-%b-%y") - end + describe "#weeks_of_month" do + it "returns the binary value 1 if frequency_modifier is set as 'first'" do + new_resource.frequency_modifier "first" + expect(provider.send(:weeks_of_month)).to eq(1) end - context "when system date format 'dd/MM/yyyy'" do - it "returns '%d/%m/%Y'" do - allow(provider).to receive(:get_system_short_date_format).and_return("dd/MM/yyyy") - expect(provider.send(:convert_system_date_format_to_ruby_date_format)).to eq("%d/%m/%Y") - end + it "returns the binary value 2 if frequency_modifier is set as 'second'" do + new_resource.frequency_modifier "second" + expect(provider.send(:weeks_of_month)).to eq(2) end - end - describe "#convert_system_date_format_to_ruby_long_date" do - context "when system date format 'dd-MMM-yy'" do - it "returns '%d-%m-%Y'" do - allow(provider).to receive(:get_system_short_date_format).and_return("dd-MMM-yy") - expect(provider.send(:convert_system_date_format_to_ruby_long_date)).to eq("%d-%m-%Y") - end + it "returns the binary value 4 if frequency_modifier is set as 'third'" do + new_resource.frequency_modifier "third" + expect(provider.send(:weeks_of_month)).to eq(4) end - context "when system date format 'dd/MM/yyyy'" do - it "returns '%d/%m/%Y'" do - allow(provider).to receive(:get_system_short_date_format).and_return("dd/MM/yyyy") - expect(provider.send(:convert_system_date_format_to_ruby_long_date)).to eq("%d/%m/%Y") - end + it "returns the binary value 8 if frequency_modifier is set as 'fourth'" do + new_resource.frequency_modifier "fourth" + expect(provider.send(:weeks_of_month)).to eq(8) end - end - describe "#common_date_format_conversion" do - context "when system date format 'dd-MM-yyyy'" do - it "returns '%d-%m-%Y'" do - expect(provider.send(:common_date_format_conversion, "dd-MM-yyyy")).to eq("%d-%m-%Y") - end + it "returns the binary value 16 if frequency_modifier is set as 'last'" do + new_resource.frequency_modifier "last" + expect(provider.send(:weeks_of_month)).to eq(nil) end - context "when system date format 'd-M-yyyy'" do - it "returns '%d-%m-%Y'" do - expect(provider.send(:common_date_format_conversion, "dd-MM-yyyy")).to eq("%d-%m-%Y") - end + it "returns the binary value 15 if frequency_modifier is set as 'first, second, third, fourth'" do + new_resource.frequency_modifier "first, second, third, fourth" + expect(provider.send(:weeks_of_month)).to eq(15) end end - describe "#update_task_xml" do - before do - new_resource.command "chef-client" - new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" - new_resource.random_delay "20" - end - - it "does nothing if the task doesn't exist" do - task_xml = double("xml", :exitstatus => 1) - allow(provider).to receive(:powershell_out).and_return(task_xml) - output = provider.send(:update_task_xml, ["random_delay"]) - expect(output).to be(nil) - end - - it "updates the task XML if random_delay is passed" do - shell_out_obj = double("xml", :exitstatus => 0, :stdout => task_xml) - allow(provider).to receive(:powershell_out).and_return(shell_out_obj) - expect(::File).to receive(:join) - expect(::File).to receive(:open) - expect(::File).to receive(:delete) - expect(provider).to receive(:run_schtasks).twice - output = provider.send(:update_task_xml, ["random_delay"]) - end - - it "updates the task XML if frequency is set as `:none`" do - new_resource.frequency :none - new_resource.random_delay "" - shell_out_obj = double("xml", :exitstatus => 0, :stdout => task_xml) - allow(provider).to receive(:powershell_out).and_return(shell_out_obj) - expect(::File).to receive(:delete) - expect(::File).to receive(:join) - expect(::File).to receive(:open) - expect(provider).to receive(:run_schtasks).twice - output = provider.send(:update_task_xml, ["random_delay"]) + # REF: https://msdn.microsoft.com/en-us/library/windows/desktop/aa382063(v=vs.85).aspx + describe "#days_of_month" do + it "returns the binary value 1 if day is set as 1" do + new_resource.day "1" + expect(provider.send(:days_of_month)).to eq(1) end - end - describe "#load_task_hash" do - it "returns false if the task doesn't exist" do - allow(provider).to receive_message_chain(:powershell_out, :stdout, :force_encoding).and_return("") - allow(provider).to receive(:load_task_xml) - expect(provider.send(:load_task_hash, "chef-client")).to be(false) + it "returns the binary value 2 if day is set as 2" do + new_resource.day "2" + expect(provider.send(:days_of_month)).to eq(2) end - it "returns task hash if the task exists" do - powershell_output = "\r\nFolder: \\\r\nHostName: NIMISHA-PC\r\nTaskName: \\chef-client\r\n" - task_h = { :"" => "", :Folder => "\\", :HostName => "NIMISHA-PC", :TaskName => "\\chef-client" } - allow(provider).to receive_message_chain(:powershell_out, :stdout, :force_encoding).and_return(powershell_output) - allow(provider).to receive(:load_task_xml).with("chef-client") - expect(provider.send(:load_task_hash, "chef-client")).to eq(task_h) + it "returns the binary value 1073741824 if day is set as 31" do + new_resource.day "31" + expect(provider.send(:days_of_month)).to eq(1073741824) + end + + it "returns the binary value 131072 if day is set as 18" do + new_resource.day "18" + expect(provider.send(:days_of_month)).to eq(131072) end end - describe "#frequency_modifier_allowed" do - it "returns true for frequency :hourly" do - new_resource.frequency :hourly - expect(provider.send(:frequency_modifier_allowed)).to be(true) + #Ref : https://msdn.microsoft.com/en-us/library/windows/desktop/aa380729(v=vs.85).aspx + describe "#days_of_week" do + it "returns the binary value 2 if day is set as 'Mon'" do + new_resource.day "Mon" + expect(provider.send(:days_of_week)).to eq(2) end - it "returns true for frequency :monthly if frequency_modifier is THIRD" do - new_resource.frequency :monthly - new_resource.frequency_modifier "THIRD" - expect(provider.send(:frequency_modifier_allowed)).to be(true) + it "returns the binary value 4 if day is set as 'Tue'" do + new_resource.day "Tue" + expect(provider.send(:days_of_week)).to eq(4) end - it "returns false for frequency :once" do - new_resource.frequency :once - expect(provider.send(:frequency_modifier_allowed)).to be(false) + it "returns the binary value 8 if day is set as 'Wed'" do + new_resource.day "Wed" + expect(provider.send(:days_of_week)).to eq(8) + end + + it "returns the binary value 16 if day is set as 'Thu'" do + new_resource.day "Thu" + expect(provider.send(:days_of_week)).to eq(16) end - it "returns false for frequency :none" do - new_resource.frequency :none - expect(provider.send(:frequency_modifier_allowed)).to be(false) + it "returns the binary value 32 if day is set as 'Fri'" do + new_resource.day "Fri" + expect(provider.send(:days_of_week)).to eq(32) + end + + it "returns the binary value 64 if day is set as 'Sat'" do + new_resource.day "Sat" + expect(provider.send(:days_of_week)).to eq(64) + end + + it "returns the binary value 1 if day is set as 'Sun'" do + new_resource.day "Sun" + expect(provider.send(:days_of_week)).to eq(1) + end + + it "returns the binary value 127 if day is set as 'Mon, tue, wed, thu, fri, sat, sun'" do + new_resource.day "Mon, tue, wed, thu, fri, sat, sun" + expect(provider.send(:days_of_week)).to eq(127) end end - # In windows_task resource sec_to_dur method converts seconds to duration in format 60 == 'PT60S' - # random_delay_updated? method use the value return by sec_to_dur as input for comparison for new_resource.random_delay mocking the same here - describe "#random_delay_updated?" do - before do - new_resource.command "chef-client" - new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" + # REf: https://msdn.microsoft.com/en-us/library/windows/desktop/aa382064(v=vs.85).aspx + describe "#monts_of_year" do + it "returns the binary value 1 if day is set as 'Jan'" do + new_resource.months "Jan" + expect(provider.send(:months_of_year)).to eq(1) end - it "returns false if current_resource.random_delay = nil & random_delay is set to '0' seconds" do - task_hash[:random_delay] = nil - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.random_delay = "PT0S" - expect(provider.send(:random_delay_updated?)).to be(false) + it "returns the binary value 2 if day is set as 'Feb'" do + new_resource.months "Feb" + expect(provider.send(:months_of_year)).to eq(2) end - it "returns false if current_resource.random_delay = 'P7D' & random_delay is set to '604800' seconds " do - task_hash[:random_delay] = "P7D" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.random_delay = "PT604800S" - expect(provider.send(:random_delay_updated?)).to be(false) + it "returns the binary value 4 if day is set as 'Mar'" do + new_resource.months "Mar" + expect(provider.send(:months_of_year)).to eq(4) end - it "returns false if current_resource.random_delay = 'P7DT1S' & random_delay is set to '604801' seconds" do - task_hash[:random_delay] = "P7DT1S" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.random_delay = "PT604801S" - expect(provider.send(:random_delay_updated?)).to be(false) + it "returns the binary value 8 if day is set as 'Apr'" do + new_resource.months "Apr" + expect(provider.send(:months_of_year)).to eq(8) end - it "returns true if current_resource.random_delay = 'PT1S' & random_delay is set to '3600' seconds" do - task_hash[:random_delay] = "PT1S" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.random_delay = "PT3600S" - expect(provider.send(:random_delay_updated?)).to be(true) + it "returns the binary value 16 if day is set as 'May'" do + new_resource.months "May" + expect(provider.send(:months_of_year)).to eq(16) end - it "returns false if current_resource.random_delay = 'P2Y1MT2H' & random_delay is set to '65707200' seconds" do - task_hash[:random_delay] = "P2Y1MT2H" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.random_delay = "PT65707200S" - expect(provider.send(:random_delay_updated?)).to be(false) + it "returns the binary value 32 if day is set as 'Jun'" do + new_resource.months "Jun" + expect(provider.send(:months_of_year)).to eq(32) + end + + it "returns the binary value 64 if day is set as 'Jul'" do + new_resource.months "Jul" + expect(provider.send(:months_of_year)).to eq(64) + end + + it "returns the binary value 128 if day is set as 'Aug'" do + new_resource.months "Aug" + expect(provider.send(:months_of_year)).to eq(128) + end + + it "returns the binary value 256 if day is set as 'Sep'" do + new_resource.months "Sep" + expect(provider.send(:months_of_year)).to eq(256) + end + + it "returns the binary value 512 if day is set as 'Oct'" do + new_resource.months "Oct" + expect(provider.send(:months_of_year)).to eq(512) + end + + it "returns the binary value 1024 if day is set as 'Nov'" do + new_resource.months "Nov" + expect(provider.send(:months_of_year)).to eq(1024) + end + + it "returns the binary value 2048 if day is set as 'Dec'" do + new_resource.months "Dec" + expect(provider.send(:months_of_year)).to eq(2048) + end + + it "returns the binary value 4095 if day is set as 'jan, Feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec'" do + new_resource.months "jan, Feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" + expect(provider.send(:months_of_year)).to eq(4095) end end - describe "#execution_time_limit_updated?" do - before do - new_resource.command "chef-client" + describe "#run_level" do + it "return binary value 1 for run_level highest" do new_resource.run_level :highest - new_resource.frequency :minute - new_resource.frequency_modifier 15 - new_resource.user "SYSTEM" + expect(provider.send(:run_level)).to be(1) end - it "returns false if current_resource.execution_time_limit = 'P7D' & execution_time_limit is set to 604800 seconds " do - task_hash[:execution_time_limit] = "P7D" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.execution_time_limit = "PT604800S" - expect(provider.send(:execution_time_limit_updated?)).to be(false) + it "return binary value 1 for run_level limited" do + new_resource.run_level :limited + expect(provider.send(:run_level)).to be(0) end + end - it "returns false if current_resource.execution_time_limit = 'P7DT1S' & execution_time_limit is set to 604801 seconds" do - task_hash[:execution_time_limit] = "P7DT1S" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.execution_time_limit = "PT604801S" - expect(provider.send(:execution_time_limit_updated?)).to be(false) + describe "#logon_type" do + it "return logon_type bindary value as 5 as if password is nil" do + new_resource.password = nil + expect(provider.send(:logon_type)).to be(5) end - it "returns true if current_resource.execution_time_limit = 'PT1S' & execution_time_limit is set to '3600' seconds" do - task_hash[:execution_time_limit] = "PT1S" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.execution_time_limit = "PT3600S" - expect(provider.send(:execution_time_limit_updated?)).to be(true) + it "return logon_type bindary value as 1 as if password is not nil" do + new_resource.password = "abc" + expect(provider.send(:logon_type)).to be(1) end + end - it "returns false if current_resource.execution_time_limit = 'P2Y1MT2H' & execution_time_limit is set to '65707200' seconds" do - task_hash[:execution_time_limit] = "P2Y1MT2H" - allow(provider).to receive(:load_task_hash).and_return(task_hash) - provider.load_current_resource - new_resource.execution_time_limit = "PT65707200S" - expect(provider.send(:execution_time_limit_updated?)).to be(false) + describe "#get_day" do + it "return day if date is provided" do + expect(provider.send(:get_day, "01/02/2018")).to eq("TUE") end end end diff --git a/spec/unit/resource/windows_task_spec.rb b/spec/unit/resource/windows_task_spec.rb index 4b74a4f78a..6dbda35274 100644 --- a/spec/unit/resource/windows_task_spec.rb +++ b/spec/unit/resource/windows_task_spec.rb @@ -18,7 +18,7 @@ require "spec_helper" -describe Chef::Resource::WindowsTask do +describe Chef::Resource::WindowsTask, :windows_only do let(:resource) { Chef::Resource::WindowsTask.new("sample_task") } it "sets resource name as :windows_task" do @@ -53,11 +53,16 @@ describe Chef::Resource::WindowsTask do expect(resource.frequency_modifier).to eql(1) end - it "sets the default frequency as :hourly" do - expect(resource.frequency).to eql(:hourly) + context "when frequency is not provided" do + it "raises ArgumentError to provide frequency" do + expect { resource.after_created }.to raise_error(ArgumentError, "Frequency needs to be provided. Valid frequencies are :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none." ) + end end context "when user is set but password is not" do + before do + resource.frequency :hourly + end it "raises an error if the user is a non-system user" do resource.user "bob" expect { resource.after_created }.to raise_error(ArgumentError, %q{Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: 'NT AUTHORITY\SYSTEM', 'SYSTEM', 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', 'USERS'}) @@ -75,10 +80,12 @@ describe Chef::Resource::WindowsTask do end context "when random_delay is passed" do - it "raises error if frequency is `:once`" do + # changed this sepc since random_delay property is valid with it frequency :once + it "not raises error if frequency is `:once`" do resource.frequency :once resource.random_delay "20" - expect { resource.after_created }.to raise_error(ArgumentError, "`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly") + resource.start_time "15:00" + expect { resource.after_created }.to_not raise_error(ArgumentError, "`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly") end it "raises error for invalid random_delay" do @@ -93,29 +100,30 @@ describe Chef::Resource::WindowsTask do expect { resource.after_created }.to raise_error(ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60').") end - it "converts seconds String into iso8601 duration format" do + it "converts '60' seconds into integer 1 minute format" do resource.frequency :monthly resource.random_delay "60" resource.after_created - expect(resource.random_delay).to eq("PT60S") + expect(resource.random_delay).to eq(1) end - it "converts seconds Integer into iso8601 duration format" do + it "converts 60 Integer into integer 1 minute format" do resource.frequency :monthly resource.random_delay 60 resource.after_created - expect(resource.random_delay).to eq("PT60S") + expect(resource.random_delay).to eq(1) end it "raises error that random_delay is not supported" do - expect { resource.send(:validate_random_delay, 60, :on_idle) }.to raise_error(ArgumentError, "`random_delay` property is supported only for frequency :minute, :hourly, :daily, :weekly and :monthly") + expect { resource.send(:validate_random_delay, 60, :on_idle) }.to raise_error(ArgumentError, "`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly") end end context "when execution_time_limit isn't specified" do - it "sets the default value to PT72H" do + it "sets the default value to PT72H which get converted to minute as 4320" do + resource.frequency :hourly resource.after_created - expect(resource.execution_time_limit).to eq("PT72H") + expect(resource.execution_time_limit).to eq(4320) end end @@ -130,16 +138,18 @@ describe Chef::Resource::WindowsTask do expect { resource.after_created }.to raise_error(ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60').") end - it "converts seconds Integer into iso8601 format" do + it "converts seconds Integer into integer minute format" do + resource.frequency :hourly resource.execution_time_limit 60 resource.after_created - expect(resource.execution_time_limit).to eq("PT60S") + expect(resource.execution_time_limit).to eq(1) end - it "converts seconds String into iso8601 format" do + it "converts seconds String into integer minute format" do + resource.frequency :hourly resource.execution_time_limit "60" resource.after_created - expect(resource.execution_time_limit).to eq("PT60S") + expect(resource.execution_time_limit).to eq(1) end end @@ -166,15 +176,23 @@ describe Chef::Resource::WindowsTask do end context "#validate_start_day" do - it "raise error if start_day is passed with invalid frequency (:on_logon)" do - expect { resource.send(:validate_start_day, "02/07/1984", :on_logon) }.to raise_error(ArgumentError, "`start_day` property is not supported with frequency: on_logon") + it "not to raise error if start_day is passed with invalid frequency (:onstart)" do + expect { resource.send(:validate_start_day, "02/07/1984", :onstart) }.not_to raise_error + end + + it "not to raise error if start_day is passed with invalid frequency (:on_idle)" do + expect { resource.send(:validate_start_day, "02/07/1984", :on_idle) }.not_to raise_error + end + + it "not to raise error if start_day is passed with invalid frequency (:on_logon)" do + expect { resource.send(:validate_start_day, "02/07/1984", :on_logon) }.not_to raise_error end - it "does not raise error if start_day is passed with valid frequency (:weekly)" do + it "not raise error if start_day is passed with valid frequency (:weekly)" do expect { resource.send(:validate_start_day, "02/07/1984", :weekly) }.not_to raise_error end - it "raise error if start_day is passed with invalid date format (DD/MM/YYYY)" do + it "not to raise error if start_day is passed with invalid date format (DD/MM/YYYY)" do expect { resource.send(:validate_start_day, "28/12/2009", :weekly) }.to raise_error(ArgumentError, "`start_day` property must be in the MM/DD/YYYY format.") end @@ -224,73 +242,73 @@ describe Chef::Resource::WindowsTask do context "when frequency is :monthly" do it "raises error if frequency_modifier > 12" do - expect { resource.send(:validate_create_frequency_modifier, :monthly, 14) }.to raise_error("frequency_modifier value 14 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'.") + expect { resource.send(:validate_create_frequency_modifier, :monthly, 14) }.to raise_error("frequency_modifier value 14 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") end it "raises error if frequency_modifier is invalid" do - expect { resource.send(:validate_create_frequency_modifier, :monthly, "abc") }.to raise_error("frequency_modifier value abc is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'.") + expect { resource.send(:validate_create_frequency_modifier, :monthly, "abc") }.to raise_error("frequency_modifier value abc is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") end end end context "#validate_create_day" do it "raises error if frequency is not :weekly or :monthly" do - expect { resource.send(:validate_create_day, "Mon", :once) }.to raise_error("day property is only valid for tasks that run monthly or weekly") + expect { resource.send(:validate_create_day, "Mon", :once, 1) }.to raise_error("day property is only valid for tasks that run monthly or weekly") end it "accepts a valid single day" do - expect { resource.send(:validate_create_day, "Mon", :weekly) }.not_to raise_error + expect { resource.send(:validate_create_day, "Mon", :weekly, 1) }.not_to raise_error end it "accepts a comma separated list of valid days" do - expect { resource.send(:validate_create_day, "Mon, tue, THU", :weekly) }.not_to raise_error + expect { resource.send(:validate_create_day, "Mon, tue, THU", :weekly, 1) }.not_to raise_error end it "raises error for invalid day value" do - expect { resource.send(:validate_create_day, "xyz", :weekly) }.to raise_error(ArgumentError, "day property invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma.") + expect { resource.send(:validate_create_day, "xyz", :weekly, 1) }.to raise_error(ArgumentError, "day property invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN, *. Multiple values must be separated by a comma.") end end context "#validate_create_months" do it "raises error if frequency is not :monthly" do - expect { resource.send(:validate_create_months, "Jan", :once) }.to raise_error(ArgumentError, "months property is only valid for tasks that run monthly") + expect { resource.send(:validate_create_months, "Jan", :once) }.to raise_error(ArgumentError, "months property is only valid for tasks that run monthly") end it "accepts a valid single month" do - expect { resource.send(:validate_create_months, "Feb", :monthly) }.not_to raise_error + expect { resource.send(:validate_create_months, "Feb", :monthly) }.not_to raise_error end it "accepts a comma separated list of valid months" do - expect { resource.send(:validate_create_months, "Jan, mar, AUG", :monthly) }.not_to raise_error + expect { resource.send(:validate_create_months, "Jan, mar, AUG", :monthly) }.not_to raise_error end it "raises error for invalid month value" do - expect { resource.send(:validate_create_months, "xyz", :monthly) }.to raise_error(ArgumentError, "months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma.") + expect { resource.send(:validate_create_months, "xyz", :monthly) }.to raise_error(ArgumentError, "months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, *. Multiple values must be separated by a comma.") end end context "#validate_idle_time" do it "raises error if frequency is not :on_idle" do [:minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :none].each do |frequency| - expect { resource.send(:validate_idle_time, 5, frequency) }.to raise_error(ArgumentError, "idle_time property is only valid for tasks that run on_idle") + expect { resource.send(:validate_idle_time, 5, frequency) }.to raise_error(ArgumentError, "idle_time property is only valid for tasks that run on_idle") end end it "raises error if idle_time > 999" do - expect { resource.send(:validate_idle_time, 1000, :on_idle) }.to raise_error(ArgumentError, "idle_time value 1000 is invalid. Valid values for :on_idle frequency are 1 - 999.") + expect { resource.send(:validate_idle_time, 1000, :on_idle) }.to raise_error(ArgumentError, "idle_time value 1000 is invalid. Valid values for :on_idle frequency are 1 - 999.") end it "raises error if idle_time < 0" do - expect { resource.send(:validate_idle_time, -5, :on_idle) }.to raise_error(ArgumentError, "idle_time value -5 is invalid. Valid values for :on_idle frequency are 1 - 999.") + expect { resource.send(:validate_idle_time, -5, :on_idle) }.to raise_error(ArgumentError, "idle_time value -5 is invalid. Valid values for :on_idle frequency are 1 - 999.") end it "raises error if idle_time is not set" do - expect { resource.send(:validate_idle_time, nil, :on_idle) }.to raise_error(ArgumentError, "idle_time value should be set for :on_idle frequency.") + expect { resource.send(:validate_idle_time, nil, :on_idle) }.to raise_error(ArgumentError, "idle_time value should be set for :on_idle frequency.") end it "does not raises error if idle_time is not set for other frequencies" do [:minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :none].each do |frequency| - expect { resource.send(:validate_idle_time, nil, frequency) }.not_to raise_error + expect { resource.send(:validate_idle_time, nil, frequency) }.not_to raise_error end end end |