module ForemanTasks require 'parse-cron' class RecurringLogic < ApplicationRecord include Authorizable belongs_to :task_group belongs_to :triggering has_many :tasks, :through => :task_group if Rails::VERSION::MAJOR < 4 has_many :task_groups, :through => :tasks, :uniq => true else has_many :task_groups, -> { distinct }, :through => :tasks end scoped_search :on => :id, :complete_value => false, :validator => ScopedSearch::Validators::INTEGER scoped_search :on => :max_iteration, :complete_value => false, :rename => :iteration_limit scoped_search :on => :iteration, :complete_value => false scoped_search :on => :cron_line, :complete_value => true before_create do task_group.save end def self.allowed_states %w[active finished cancelled failed] end def start(action_class, *args) self.state = 'active' save! trigger_repeat(action_class, *args) end def trigger_repeat(action_class, *args) if can_continue? self.iteration += 1 save! ::ForemanTasks.delay action_class, generate_delay_options, *args else self.state = 'finished' save! nil end end def cancel self.state = 'cancelled' save! tasks.active.each(&:cancel) end def next_occurrence_time(time = Time.zone.now) @parser ||= CronParser.new(cron_line, Time.zone) @parser.next(time) end def generate_delay_options(time = Time.zone.now, options = {}) { :start_at => next_occurrence_time(time), :start_before => options['start_before'], :recurring_logic_id => id } end def valid?(*_) cron_line.present? && valid_cronline? && !state.nil? || can_start? end def valid_cronline? !!next_occurrence_time rescue ArgumentError => _ false end def can_start?(time = Time.zone.now) (end_time.nil? || next_occurrence_time(time) < end_time) && (max_iteration.nil? || iteration < max_iteration) end def can_continue?(time = Time.zone.now) state == 'active' && can_start?(time) end def finished? state == 'finished' end def humanized_state case state when 'active' N_('Active') when 'cancelled' N_('Cancelled') when 'finished' N_('Finished') else N_('N/A') end end def self.assemble_cronline(hash) hash.values_at(:minutes, :hours, :days, :months, :days_of_week) .map { |value| value.nil? || value.blank? ? '*' : value } .join(' ') end def self.new_from_cronline(cronline) new.tap do |logic| logic.cron_line = cronline logic.task_group = ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup.new end end def self.new_from_triggering(triggering) cronline = if triggering.input_type == :cronline triggering.cronline else ::ForemanTasks::RecurringLogic.assemble_cronline(cronline_hash(triggering.input_type, triggering.time, triggering.days_of_week)) end ::ForemanTasks::RecurringLogic.new_from_cronline(cronline).tap do |manager| manager.end_time = triggering.end_time if triggering.end_time_limited.present? manager.max_iteration = triggering.max_iteration if triggering.max_iteration.present? manager.triggering = triggering end end def self.cronline_hash(recurring_type, time_hash, days_of_week_hash) hash = Hash[[:years, :months, :days, :hours, :minutes].zip(time_hash.values)] hash.update :days_of_week => days_of_week_hash .select { |_key, value| value == '1' } .keys.join(',') allowed_keys = case recurring_type when :monthly [:minutes, :hours, :days] when :weekly [:minutes, :hours, :days_of_week] when :daily [:minutes, :hours] when :hourly [:minutes] end hash.select { |key, _| allowed_keys.include? key } end end end