foreman-tasks 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
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