diff options
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/resource/windows_task.rb | 461 |
1 files changed, 230 insertions, 231 deletions
diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb index f871708ea2..5bd6d7e557 100644 --- a/lib/chef/resource/windows_task.rb +++ b/lib/chef/resource/windows_task.rb @@ -22,319 +22,318 @@ require "chef/win32/security" if Chef::Platform.windows? class Chef class Resource class WindowsTask < Chef::Resource - if Chef::Platform.windows? - resource_name :windows_task - provides(:windows_task) { true } + resource_name :windows_task + provides(:windows_task) { true } - description "Use the windows_task resource to create, delete or run a Windows scheduled task. Requires Windows Server 2008 or later due to API usage." - introduced "13.0" + description "Use the windows_task resource to create, delete or run a Windows scheduled task. Requires Windows Server 2008 or later due to API usage." + introduced "13.0" - allowed_actions :create, :delete, :run, :end, :enable, :disable, :change - default_action :create + allowed_actions :create, :delete, :run, :end, :enable, :disable, :change + default_action :create - property :task_name, String, regex: [/\A[^\/\:\*\?\<\>\|]+\z/], - description: "The task name, such as 'Task Name' or '/Task Name'", - name_property: true + property :task_name, String, regex: [/\A[^\/\:\*\?\<\>\|]+\z/], + description: "The task name, such as 'Task Name' or '/Task Name'", + name_property: true - property :command, String, - description: "The command to be executed by the windows scheduled task." + property :command, String, + description: "The command to be executed by the windows scheduled task." - property :cwd, String, - description: "The directory the task will be run from." + property :cwd, String, + description: "The directory the task will be run from." - property :user, String, - description: "The user to run the task as.", - default: Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name + property :user, String, + description: "The user to run the task as.", + default: lazy { Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name if Chef::Platform.windows? }, + default_description: "The localized SYSTEM user for the node." - property :password, String, - description: "The user’s password. The user property must be set if using this property." + property :password, String, + description: "The user’s password. The user property must be set if using this property." - property :run_level, Symbol, equal_to: [:highest, :limited], - description: "Run with ':limited' or ':highest' privileges.", - default: :limited + property :run_level, Symbol, equal_to: [:highest, :limited], + description: "Run with ':limited' or ':highest' privileges.", + default: :limited - property :force, [TrueClass, FalseClass], - description: "When used with create, will update the task.", - default: false + property :force, [TrueClass, FalseClass], + description: "When used with create, will update the task.", + default: false - property :interactive_enabled, [TrueClass, FalseClass], - description: "Allow task to run interactively or non-interactively. Requires user and password to also be set.", - default: false + property :interactive_enabled, [TrueClass, FalseClass], + description: "Allow task to run interactively or non-interactively. Requires user and password to also be set.", + default: false - property :frequency_modifier, [Integer, String], - default: 1 + property :frequency_modifier, [Integer, String], + default: 1 - property :frequency, Symbol, equal_to: [:minute, - :hourly, - :daily, - :weekly, - :monthly, - :once, - :on_logon, - :onstart, - :on_idle, - :none], - description: "The frequency with which to run the task." + property :frequency, Symbol, equal_to: [:minute, + :hourly, + :daily, + :weekly, + :monthly, + :once, + :on_logon, + :onstart, + :on_idle, + :none], + description: "The frequency with which to run the task." - property :start_day, String, - description: "Specifies the first date on which the task runs in MM/DD/YYYY format." + property :start_day, String, + description: "Specifies the first date on which the task runs in MM/DD/YYYY format." - property :start_time, String, - description: "Specifies the start time to run the task, in HH:mm format." + property :start_time, String, + description: "Specifies the start time to run the task, in HH:mm format." - property :day, [String, Integer], - description: "The day(s) on which the task runs." + property :day, [String, Integer], + description: "The day(s) on which the task runs." - property :months, String, - description: "The Months of the year on which the task runs, such as: 'JAN, FEB' or '\*'. Multiple months should be comma delimited. e.g. 'Jan, Feb, Mar, Dec'." + property :months, String, + description: "The Months of the year on which the task runs, such as: 'JAN, FEB' or '\*'. Multiple months should be comma delimited. e.g. 'Jan, Feb, Mar, Dec'." - property :idle_time, Integer, - description: "For :on_idle frequency, the time (in minutes) without user activity that must pass to trigger the task, from 1 - 999." + property :idle_time, Integer, + description: "For :on_idle frequency, the time (in minutes) without user activity that must pass to trigger the task, from 1 - 999." - property :random_delay, [String, Integer], - description: "Delays the task up to a given time (in seconds)." + property :random_delay, [String, Integer], + description: "Delays the task up to a given time (in seconds)." - property :execution_time_limit, [String, Integer], - description: "The maximum time (in seconds) the task will run.", - default: "PT72H" # 72 hours in ISO8601 duration format + property :execution_time_limit, [String, Integer], + description: "The maximum time (in seconds) the task will run.", + default: "PT72H" # 72 hours in ISO8601 duration format - property :minutes_duration, [String, Integer], - description: "" + property :minutes_duration, [String, Integer], + description: "" - property :minutes_interval, [String, Integer], - description: "" + property :minutes_interval, [String, Integer], + description: "" - property :priority, Integer, - description: "Use to set Priority Levels range from 0 to 10.", - default: 7, callbacks: { "should be in range of 0 to 10" => proc { |v| v >= 0 && v <= 10 } } + property :priority, Integer, + description: "Use to set Priority Levels range from 0 to 10.", + default: 7, callbacks: { "should be in range of 0 to 10" => proc { |v| v >= 0 && v <= 10 } } - property :disallow_start_if_on_batteries, [TrueClass, FalseClass], - introduced: "14.4", default: false, - description: "Disallow start of the task if the system is running on battery power." + property :disallow_start_if_on_batteries, [TrueClass, FalseClass], + introduced: "14.4", default: false, + description: "Disallow start of the task if the system is running on battery power." - property :stop_if_going_on_batteries, [TrueClass, FalseClass], - introduced: "14.4", default: false, - description: "Scheduled task option when system is switching on battery." + property :stop_if_going_on_batteries, [TrueClass, FalseClass], + introduced: "14.4", default: false, + description: "Scheduled task option when system is switching on battery." - property :description, String, - introduced: "14.7", - description: "The task description." + property :description, String, + introduced: "14.7", + description: "The task description." - attr_accessor :exists, :task, :command_arguments + attr_accessor :exists, :task, :command_arguments - VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * }.freeze - 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 *}.freeze - VALID_WEEKS = %w{FIRST SECOND THIRD FOURTH LAST LASTDAY}.freeze + VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * }.freeze + 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 *}.freeze + VALID_WEEKS = %w{FIRST SECOND THIRD FOURTH LAST LASTDAY}.freeze - def after_created - if random_delay - validate_random_delay(random_delay, frequency) - random_delay(sec_to_min(random_delay)) - end - - if execution_time_limit - 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 + def after_created + if random_delay + validate_random_delay(random_delay, frequency) + random_delay(sec_to_min(random_delay)) + end - validate_frequency(frequency) if action.include?(:create) || action.include?(:change) - 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) 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 + if execution_time_limit + 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 - private + validate_frequency(frequency) if action.include?(:create) || action.include?(:change) + 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) 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 + 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) - rescue ArgumentError - false - end + def numeric_value_in_string?(val) + return true if Integer(val) + rescue ArgumentError + 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 + 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 + 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 - # 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? + # 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 - def validate_random_delay(random_delay, frequency) - 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 + # 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 - 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) + def validate_random_delay(random_delay, frequency) + 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) + end + # @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 start_day && frequency == :none - raise ArgumentError, "`start_day` property is not supported with frequency: #{frequency}" - end + def validate_start_day(start_day, 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 - 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 + # make sure the start_day is in MM/DD/YYYY format: http://rubular.com/r/cgjHemtWl5 + 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 =~ - def validate_start_time(start_time, frequency) - if start_time - raise ArgumentError, "`start_time` property is not supported with `frequency :none`" if frequency == :none - raise ArgumentError, "`start_time` property must be in the HH:mm format (e.g. 6:20pm -> 18:20)." unless /^[0-2][0-9]:[0-5][0-9]$/ =~ start_time - else - raise ArgumentError, "`start_time` needs to be provided with `frequency :once`" if frequency == :once - end + def validate_start_time(start_time, frequency) + if start_time + raise ArgumentError, "`start_time` property is not supported with `frequency :none`" if frequency == :none + raise ArgumentError, "`start_time` property must be in the HH:mm format (e.g. 6:20pm -> 18:20)." unless /^[0-2][0-9]:[0-5][0-9]$/ =~ start_time + else + raise ArgumentError, "`start_time` needs to be provided with `frequency :once`" if frequency == :once end + end - def validate_user_and_password(user, password) - if password_required?(user) && password.nil? - raise ArgumentError, "Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: '#{Chef::ReservedNames::Win32::Security::SID::SYSTEM_USER.join("', '")}'" - end + def validate_user_and_password(user, password) + if password_required?(user) && password.nil? + raise ArgumentError, "Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: '#{Chef::ReservedNames::Win32::Security::SID::SYSTEM_USER.join("', '")}'" end + end - def password_required?(user) - return false if user.nil? - @password_required ||= !Chef::ReservedNames::Win32::Security::SID.system_user?(user) - end + def password_required?(user) + return false if user.nil? + @password_required ||= !Chef::ReservedNames::Win32::Security::SID.system_user?(user) + end + + def validate_interactive_setting(interactive_enabled, password) + raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." if interactive_enabled && password.nil? + end - def validate_interactive_setting(interactive_enabled, password) - raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." if interactive_enabled && password.nil? + def validate_create_frequency_modifier(frequency, frequency_modifier) + if ([:on_logon, :onstart, :on_idle, :none].include?(frequency)) && ( frequency_modifier != 1) + raise ArgumentError, "frequency_modifier property not supported with frequency :#{frequency}" end - def validate_create_frequency_modifier(frequency, frequency_modifier) - if ([:on_logon, :onstart, :on_idle, :none].include?(frequency)) && ( frequency_modifier != 1) - raise ArgumentError, "frequency_modifier property not supported with frequency :#{frequency}" + 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 - - 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 + 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, 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(",") - 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 + 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(",") + 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" 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 + def validate_create_months(months, frequency) + 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 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" - end - if idle_time.nil? && frequency == :on_idle - raise ArgumentError, "idle_time value should be set for :on_idle frequency." - end - unless idle_time.nil? || idle_time > 0 && idle_time <= 999 - raise ArgumentError, "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999." - 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" + end + if idle_time.nil? && frequency == :on_idle + raise ArgumentError, "idle_time value should be set for :on_idle frequency." end + unless idle_time.nil? || idle_time > 0 && idle_time <= 999 + raise ArgumentError, "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999." + end + end # Converts the number of seconds to an ISO8601 duration format and returns it. # Ref : https://github.com/arnau/ISO8601/blob/master/lib/iso8601/duration.rb#L18-L23 # e.g. # ISO8601::Duration.new(65707200).to_s # returns 'PT65707200S' - def sec_to_dur(seconds) - ISO8601::Duration.new(seconds.to_i).to_s - end + def sec_to_dur(seconds) + ISO8601::Duration.new(seconds.to_i).to_s + end - def sec_to_min(seconds) - seconds.to_i / 60 - end + def sec_to_min(seconds) + seconds.to_i / 60 end end end |