foreman-tasks 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +5 -13
  2. data/app/assets/javascripts/trigger_form.js +41 -0
  3. data/app/controllers/foreman_tasks/recurring_logics_controller.rb +33 -0
  4. data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +144 -0
  5. data/app/helpers/foreman_tasks/tasks_helper.rb +8 -0
  6. data/app/lib/actions/base.rb +4 -0
  7. data/app/lib/actions/entry_action.rb +5 -1
  8. data/app/lib/actions/foreman/host/import_facts.rb +4 -0
  9. data/app/lib/actions/middleware/inherit_task_groups.rb +39 -0
  10. data/app/lib/actions/middleware/recurring_logic.rb +42 -0
  11. data/app/models/foreman_tasks/recurring_logic.rb +136 -0
  12. data/app/models/foreman_tasks/task.rb +10 -0
  13. data/app/models/foreman_tasks/task_group.rb +16 -0
  14. data/app/models/foreman_tasks/task_group_member.rb +8 -0
  15. data/app/models/foreman_tasks/task_groups/recurring_logic_task_group.rb +16 -0
  16. data/app/models/foreman_tasks/triggering.rb +99 -0
  17. data/app/views/common/_trigger_form.html.erb +14 -0
  18. data/app/views/foreman_tasks/recurring_logics/_tab_related.html.erb +3 -0
  19. data/app/views/foreman_tasks/recurring_logics/index.html.erb +34 -0
  20. data/app/views/foreman_tasks/recurring_logics/show.html.erb +12 -0
  21. data/app/views/foreman_tasks/task_groups/_common.html.erb +12 -0
  22. data/app/views/foreman_tasks/task_groups/_detail.html.erb +7 -0
  23. data/app/views/foreman_tasks/task_groups/_tab_related.html.erb +17 -0
  24. data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +39 -0
  25. data/config/routes.rb +6 -0
  26. data/db/migrate/20150814204140_add_task_type_value_index.rb +5 -0
  27. data/db/migrate/20150907124936_create_recurring_logic.rb +11 -0
  28. data/db/migrate/20150907131503_create_task_groups.rb +19 -0
  29. data/db/migrate/20151022123457_add_recurring_logic_state.rb +9 -0
  30. data/db/migrate/20151112152108_create_triggerings.rb +18 -0
  31. data/lib/foreman_tasks/engine.rb +12 -0
  32. data/lib/foreman_tasks/tasks/export_tasks.rake +2 -2
  33. data/lib/foreman_tasks/version.rb +1 -1
  34. data/test/unit/recurring_logic_test.rb +74 -0
  35. data/test/unit/task_groups_test.rb +76 -0
  36. metadata +61 -29
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZWM5N2ZhYzFhMzkxNDFhYTk2MGE4ZGRkNzNjNzRiZTlkNjJmNDdhNQ==
5
- data.tar.gz: !binary |-
6
- ODZiMDI1YmY1MDQ1ODBiMGZiZjE0NjMwNDhiNGUzMzY3YmIwM2FjMw==
2
+ SHA1:
3
+ metadata.gz: 098fddc6fc2af891ed604780f543137d9e49e820
4
+ data.tar.gz: 9ae880cb667e08207995dd5e156c9960239f339e
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MjIxYzA5NDE2YTlhMzVlZDdhYWFhOWY2ODg2MWNjNTI5ZjkyM2ZlMWU4MmY1
10
- MzYzNGVjZTM4NDUyYTc2NDEwNWMxMjQyODdlYjU1YjM4ZjA5ZWRiYmExZjNj
11
- NzI2OTkzMGIzZjA5MDI2YTFkMTUzZjdkNDdkY2RhMTM5MjkwZWY=
12
- data.tar.gz: !binary |-
13
- NDNkMGZlMDkzMTg1MjQ4YmUxZmRhNGFhZTQ5N2Y4YzUxMTBiM2EyZTIzMDIw
14
- MGZlM2E0NDQyYzRiZTIwNDU2ZDBjNzQ5OGI1NmFlNTMyZGE2OWVmMGM2NzBl
15
- YWMzNDUyMzViYWMxYmI4MjcwOWExNTFlYzI3NmM2MThmODRlZjk=
6
+ metadata.gz: 71a4d2529300e104363a53dac9c534e3a682cac5f14fe722076c7d29df48429ef0ae9f777633135b7575b68288b725b9176a449018c0276d159becd5fb0ee4fe
7
+ data.tar.gz: 39364230aa8cd621e922c377685f85cb570af75dec61ccb5105942e82793e52d71bdd061ba544200227d17215b890ad9312109c51a5f4246826e21f7823a05c2
@@ -0,0 +1,41 @@
1
+ function trigger_form_selector_binds(form_name, form_object_name) {
2
+ var form = $('form#' + form_name);
3
+ var trigger_mode_selector = form.find('input.trigger_mode_selector');
4
+ var input_type_selector = form.find('.form-control#input_type_selector');
5
+
6
+ trigger_mode_selector.on('click', function () {
7
+ ["future", "immediate", "recurring"].forEach(function(type) {
8
+ form.find('fieldset.trigger_mode_form#trigger_mode_' + type).hide();
9
+ });
10
+ form.find('fieldset.trigger_mode_form#trigger_mode_' + $(this).val()).show();
11
+ });
12
+
13
+ input_type_selector.on('change', function () {
14
+ var fieldset = form.find('fieldset.trigger_mode_form#trigger_mode_recurring');
15
+ ['cronline', 'monthly', 'weekly', 'hourly', 'daily'].forEach(function(type) {
16
+ fieldset.find('fieldset.input_type_form#input_type_' + type).hide();
17
+ })
18
+ fieldset.find('fieldset.input_type_form#input_type_' + $(this).val()).show();
19
+ var timepicker = fieldset.find('fieldset.input_type_form#time_picker');
20
+ if($(this).val() == 'cronline') {
21
+ timepicker.hide();
22
+ } else {
23
+ var hour_picker = timepicker.find('#s2id_triggering_time_time_4i');
24
+ if($(this).val() == 'hourly') {
25
+ hour_picker.hide();
26
+ } else {
27
+ hour_picker.show();
28
+ }
29
+ timepicker.show();
30
+ }
31
+ });
32
+
33
+ form.find('input.end_time_limit_selector').on('click', function() {
34
+ var o = form.find('fieldset#trigger_mode_recurring fieldset#end_time_limit_form');
35
+ if($(this).val() === 'true') {
36
+ o.show();
37
+ } else {
38
+ o.hide();
39
+ };
40
+ });
41
+ };
@@ -0,0 +1,33 @@
1
+ module ForemanTasks
2
+ class RecurringLogicsController < ::ApplicationController
3
+
4
+ before_filter :find_recurring_logic, :only => [:show, :cancel]
5
+
6
+ def index
7
+ @recurring_logics = filter(resource_base)
8
+ end
9
+
10
+ def show
11
+ end
12
+
13
+ def cancel
14
+ @recurring_logic.cancel
15
+ redirect_to :action => :index
16
+ end
17
+
18
+ def controller_name
19
+ 'foreman_tasks_recurring_logics'
20
+ end
21
+
22
+ private
23
+
24
+ def find_recurring_logic
25
+ @recurring_logic ||= ::ForemanTasks::RecurringLogic.find(params[:id])
26
+ end
27
+
28
+ def filter(scope)
29
+ scope.search_for(params[:search]).paginate(:page => params[:page])
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,144 @@
1
+ module ForemanTasks
2
+ module ForemanTasksHelper
3
+
4
+ def recurring_logic_state(recurring_logic)
5
+ icon, status = case recurring_logic.state
6
+ when 'active'
7
+ 'glyphicon-info-sign'
8
+ when 'finished'
9
+ ['glyphicon-ok-sign', 'status-ok']
10
+ when 'cancelled'
11
+ ['glyphicon-warning-sign', 'status-error']
12
+ else
13
+ 'glyphicon-question-sign'
14
+ end
15
+ content_tag(:i, '&nbsp'.html_safe, :class => "glyphicon #{icon}") + content_tag(:span, recurring_logic.humanized_state, :class => status)
16
+ end
17
+
18
+ def recurring_logic_action_buttons(recurring_logic)
19
+ buttons = []
20
+ if authorized_for(:permission => :edit_recurring_logics, :auth_object => recurring_logic)
21
+ buttons << link_to(N_("Cancel"), cancel_foreman_tasks_recurring_logic_path(recurring_logic), :method => :post, :class => 'btn btn-danger') unless %w(cancelled finished).include? recurring_logic.state
22
+ end
23
+ button_group buttons
24
+ end
25
+
26
+ def recurring_logic_next_occurrence(recurring_logic)
27
+ if %w(finished cancelled).include? recurring_logic.state
28
+ N_('-')
29
+ else
30
+ recurring_logic.next_occurrence_time
31
+ end
32
+ end
33
+
34
+ def time_f(f, attr, field_options = {}, time_options = {}, html_options = {})
35
+ f.fields_for attr do |fields|
36
+ field(fields, attr, field_options) do
37
+ fields.time_select attr, time_options, html_options
38
+ end
39
+ end
40
+ end
41
+
42
+ def datetime_f(f, attr, field_options = {}, datetime_options = {}, html_options = {})
43
+ f.fields_for attr do |fields|
44
+ field(fields, attr, field_options) do
45
+ fields.datetime_select attr, datetime_options, html_options
46
+ end
47
+ end
48
+ end
49
+
50
+ def inline_checkboxes_f(f, attr, field_options = {}, checkboxes = {}, options = {})
51
+ field(f, attr, field_options) do
52
+ checkboxes.map do |key, name|
53
+ [f.check_box(key, options), " #{name} "]
54
+ end.flatten.join('')
55
+ end
56
+ end
57
+
58
+ def trigger_selector(f, triggering = Triggering.new, options = {})
59
+ render :partial => 'common/trigger_form', :locals => { :f => f, :triggering => triggering }
60
+ end
61
+
62
+ private
63
+
64
+ def future_mode_fieldset(f, triggering)
65
+ tags = []
66
+ tags << text_f(f, :start_at_raw, :label => _('Start at'), :placeholder => 'YYYY-mm-dd HH:MM')
67
+ tags << text_f(f, :start_before_raw, :label => _('Start before'), :placeholder => 'YYYY-mm-dd HH:MM')
68
+ content_tag(:fieldset, nil, :id => "trigger_mode_future", :class => "trigger_mode_form #{'hidden' unless triggering.future?}") do
69
+ tags.join.html_safe
70
+ end
71
+ end
72
+
73
+ def recurring_mode_fieldset(f, triggering)
74
+ tags = []
75
+ tags << selectable_f(f, :input_type, %w(cronline monthly weekly daily hourly), {}, :label => _("Repeats"), :id => 'input_type_selector')
76
+ tags += [
77
+ cronline_fieldset(f, triggering),
78
+ monthly_fieldset(f, triggering),
79
+ weekly_fieldset(f, triggering),
80
+ time_picker_fieldset(f, triggering)
81
+ ]
82
+
83
+ content_tag(:fieldset, nil, :id => 'trigger_mode_recurring', :class => "trigger_mode_form #{'hidden' unless triggering.recurring?}") do
84
+ tags.join.html_safe
85
+ end
86
+ end
87
+
88
+ def cronline_fieldset(f, triggering)
89
+ options = [
90
+ _('is minute (range: 0-59)'),
91
+ _('is hour (range: 0-23)'),
92
+ _('is day of month (range: 1-31)'),
93
+ _('is month (range: 1-12)'),
94
+ _('is day of week (range: 0-6)')
95
+ ].map { |opt| content_tag(:li, opt) }.join
96
+ help = content_tag(:span, nil, :class => 'help-inline') do
97
+ popover(_('Explanation'),
98
+ _("Cron line format 'a b c d e', where:<br><ol type=\"a\">#{options}</ol>"))
99
+ end
100
+ content_tag(:fieldset, nil, :class => "input_type_form #{'hidden' unless triggering.input_type == :cronline}", :id => "input_type_cronline") do
101
+ text_f f, :cronline, :label => _('Cron line'), :placeholder => '* * * * *', :help_inline => help
102
+ end
103
+ end
104
+
105
+ def monthly_fieldset(f, triggering)
106
+ content_tag(:fieldset, nil, :id => "input_type_monthly", :class => "input_type_form #{'hidden' unless triggering.input_type == :monthly}") do
107
+ text_f(f, :days, :label => _('Days'), :placeholder => '1,2...')
108
+ end
109
+ end
110
+
111
+ def weekly_fieldset(f, triggering)
112
+ content_tag(:fieldset, nil, :id => 'input_type_weekly', :class => "input_type_form #{'hidden' unless triggering.input_type == :weekly}") do
113
+ f.fields_for :days_of_week do |days_of_week|
114
+ inline_checkboxes_f(days_of_week,
115
+ :weekday,
116
+ { :label => _("Days of week") },
117
+ { 1 => _("Mon"),
118
+ 2 => _("Tue"),
119
+ 3 => _("Wed"),
120
+ 4 => _("Thu"),
121
+ 5 => _("Fri"),
122
+ 6 => _("Sat"),
123
+ 7 => _("Sun") })
124
+ end
125
+ end
126
+ end
127
+
128
+ def time_picker_fieldset(f, triggering)
129
+ tags = []
130
+ tags << content_tag(:fieldset, nil, :id => 'time_picker', :class => "input_type_form #{'hidden' if triggering.input_type == :cronline}") do
131
+ time_f(f, :time, { :label => _("At"), :id => 'something' }, { :time_separator => '' })
132
+ end
133
+ tags << number_f(f, :max_iteration, :label => _('Repeat N times'), :min => 1, :placeholder => 'N')
134
+ tags << field(f, :end_time_limit_select, :label => _("Ends"), :control_group_id => "end_time_limit_select") do
135
+ radio_button_f(f, :end_time_limited, :value => false, :checked=> true, :text => _("Never"), :class => 'end_time_limit_selector') +
136
+ radio_button_f(f, :end_time_limited, :value => true, :text => _("On"), :class => 'end_time_limit_selector')
137
+ end
138
+ tags << content_tag(:fieldset, nil, :id => 'end_time_limit_form', :class => "input_type_form #{'hidden' unless triggering.end_time_limited}") do
139
+ datetime_f f, :end_time, { :label => _("Ends at") }, { :use_month_numbers => true, :use_two_digit_numbers => true, :time_separator => '' }
140
+ end
141
+ tags.join.html_safe
142
+ end
143
+ end
144
+ end
@@ -12,5 +12,13 @@ module ForemanTasks
12
12
  end.join('; ')
13
13
  parts.join(" ")
14
14
  end
15
+
16
+ def format_recurring_logic_limit(thing)
17
+ if thing.nil?
18
+ content_tag(:i, N_('Unlimited'))
19
+ else
20
+ thing
21
+ end
22
+ end
15
23
  end
16
24
  end
@@ -55,5 +55,9 @@ module Actions
55
55
  ForemanTasks::Task::DynflowTask.for_action(self.class).
56
56
  running.where('external_id != ?', execution_plan_id).any?
57
57
  end
58
+
59
+ def serializer_class
60
+ ::Actions::Serializers::ActiveRecordSerializer
61
+ end
58
62
  end
59
63
  end
@@ -57,7 +57,11 @@ module Actions
57
57
  end
58
58
 
59
59
  def delay(_schedule_options, *args)
60
- Serializers::ActiveRecordSerializer.new args
60
+ self.serializer_class.new args
61
+ end
62
+
63
+ def self.serializer_class
64
+ Serializers::ActiveRecordSerializer
61
65
  end
62
66
 
63
67
  end
@@ -27,6 +27,10 @@ module Actions
27
27
  raise e unless e.code == 'ERF51-9911'
28
28
  end
29
29
 
30
+ def rescue_strategy
31
+ ::Dynflow::Action::Rescue::Skip
32
+ end
33
+
30
34
  def humanized_name
31
35
  _("Import facts")
32
36
  end
@@ -0,0 +1,39 @@
1
+ module Actions
2
+ module Middleware
3
+ class InheritTaskGroups < Dynflow::Middleware
4
+
5
+ def delay(*args)
6
+ pass *args
7
+ end
8
+
9
+ def plan(*args)
10
+ inherit_task_groups
11
+ pass *args
12
+ end
13
+
14
+ def run(*args)
15
+ pass *args
16
+ collect_children_task_groups
17
+ end
18
+
19
+ def finalize
20
+ pass
21
+ end
22
+
23
+ private
24
+
25
+ def inherit_task_groups
26
+ task.add_missing_task_groups(task.parent_task.task_groups) if task.parent_task
27
+ end
28
+
29
+ def collect_children_task_groups
30
+ task.add_missing_task_groups task.sub_tasks.map(&:task_groups).flatten
31
+ end
32
+
33
+ def task
34
+ @task ||= action.task
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,42 @@
1
+ module Actions
2
+ module Middleware
3
+ class RecurringLogic < ::Dynflow::Middleware
4
+
5
+ # ::Actions::Middleware::RecurringLogic
6
+ #
7
+ # A middleware designed to make action repeatable.
8
+ # After an action is delayed, it checks whether the delay_options
9
+ # hash contains an id of a recurring logic. If so, it adds the task
10
+ # to the recurring logic's task group, otherwise does nothing.
11
+ #
12
+ # After the action's plan phase the middleware checks if the task
13
+ # is associated with a task group of any recurring logic, in which case
14
+ # it triggers another repeat using the task group's recurring logic,
15
+ # otherwise does nothing.
16
+ def delay(delay_options, *args)
17
+ pass(delay_options, *args).tap do
18
+ if delay_options[:recurring_logic_id]
19
+ task.add_missing_task_groups(recurring_logic(delay_options).task_group)
20
+ end
21
+ end
22
+ end
23
+
24
+ def plan(*args)
25
+ pass(*args).tap do
26
+ task_group = task.task_groups.find { |tg| tg.is_a? ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup }
27
+ task_group.recurring_logic.trigger_repeat(action.class, *args) if task_group
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def recurring_logic(delay_options)
34
+ ForemanTasks::RecurringLogic.find(delay_options[:recurring_logic_id])
35
+ end
36
+
37
+ def task
38
+ @task ||= action.task
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,136 @@
1
+ module ForemanTasks
2
+
3
+ require 'parse-cron'
4
+
5
+ class RecurringLogic < ActiveRecord::Base
6
+ include Authorizable
7
+
8
+ belongs_to :task_group
9
+ belongs_to :triggering
10
+
11
+ has_many :tasks, :through => :task_group
12
+ has_many :task_groups, :through => :tasks, :uniq => true
13
+
14
+ validates :cron_line, :presence => true
15
+
16
+ scoped_search :on => :id, :complete_value => false
17
+ scoped_search :on => :max_iteration, :complete_value => false, :rename => :iteration_limit
18
+ scoped_search :on => :iteration, :complete_value => false
19
+ scoped_search :on => :cron_line, :complete_value => true
20
+
21
+ before_create do
22
+ self.task_group.save
23
+ end
24
+
25
+ def self.allowed_states
26
+ %w(active finished cancelled failed)
27
+ end
28
+
29
+ def start(action_class, *args)
30
+ self.state = 'active'
31
+ save!
32
+ trigger_repeat(action_class, *args)
33
+ end
34
+
35
+ def trigger_repeat(action_class, *args)
36
+ unless can_continue?
37
+ self.state = 'finished'
38
+ save!
39
+ return
40
+ else
41
+ self.iteration += 1
42
+ save!
43
+ ::ForemanTasks.delay action_class,
44
+ generate_delay_options,
45
+ *args
46
+ end
47
+ end
48
+
49
+ def cancel
50
+ self.state = 'cancelled'
51
+ save!
52
+ tasks.active.each(&:cancel)
53
+ end
54
+
55
+ def next_occurrence_time(time = Time.now)
56
+ @parser ||= CronParser.new(cron_line)
57
+ @parser.next(time)
58
+ end
59
+
60
+ def generate_delay_options(time = Time.now, options = {})
61
+ {
62
+ :start_at => next_occurrence_time(time),
63
+ :start_before => options['start_before'],
64
+ :recurring_logic_id => self.id
65
+ }
66
+ end
67
+
68
+ def can_continue?(time = Time.now)
69
+ self.state == 'active' &&
70
+ (end_time.nil? || next_occurrence_time(time) < end_time) &&
71
+ (max_iteration.nil? || iteration < max_iteration)
72
+ end
73
+
74
+ def finished?
75
+ self.state == 'finished'
76
+ end
77
+
78
+ def humanized_state
79
+ case self.state
80
+ when 'active'
81
+ N_('Active')
82
+ when 'cancelled'
83
+ N_('Cancelled')
84
+ when 'finished'
85
+ N_('Finished')
86
+ else
87
+ N_('N/A')
88
+ end
89
+ end
90
+
91
+ def self.assemble_cronline(hash)
92
+ hash.values_at(*[:minutes, :hours, :days, :months, :days_of_week])
93
+ .map { |value| (value.nil? || value.blank?) ? '*' : value }
94
+ .join(' ')
95
+ end
96
+
97
+ def self.new_from_cronline(cronline)
98
+ self.new.tap do |logic|
99
+ logic.cron_line = cronline
100
+ logic.task_group = ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup.new
101
+ end
102
+ end
103
+
104
+ def self.new_from_triggering(triggering)
105
+ cronline = if triggering.input_type == :cronline
106
+ triggering.cronline
107
+ else
108
+ ::ForemanTasks::RecurringLogic.assemble_cronline(cronline_hash triggering.input_type, triggering.time, triggering.days_of_week)
109
+ end
110
+ ::ForemanTasks::RecurringLogic.new_from_cronline(cronline).tap do |manager|
111
+ manager.end_time = Time.new(*recurring_options.end_time.values) if triggering.end_time_limited
112
+ manager.max_iteration = triggering.max_iteration unless triggering.max_iteration.blank?
113
+ manager.triggering = triggering
114
+ end
115
+ end
116
+
117
+ def self.cronline_hash(recurring_type, time_hash, days_of_week_hash)
118
+ hash = Hash[[:years, :months, :days, :hours, :minutes].zip(time_hash.values)]
119
+
120
+ hash.update :days_of_week => days_of_week_hash
121
+ .select { |value, index| value == "1" }
122
+ .values.join(',')
123
+ allowed_keys = case recurring_type
124
+ when :monthly
125
+ [:minutes, :hours, :days]
126
+ when :weekly
127
+ [:minutes, :hours, :days_of_week]
128
+ when :daily
129
+ [:minutes, :hours]
130
+ when :hourly
131
+ [:minutes]
132
+ end
133
+ hash.select { |key, _| allowed_keys.include? key }
134
+ end
135
+ end
136
+ end