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
@@ -13,6 +13,10 @@ module ForemanTasks
13
13
  has_many :sub_tasks, :class_name => 'ForemanTasks::Task', :foreign_key => :parent_task_id
14
14
  has_many :locks
15
15
 
16
+ has_many :task_group_members
17
+ has_many :task_groups, :through => :task_group_members
18
+ has_many :recurring_logic_task_groups, :through => :task_group_members, :conditions => { :type => 'ForemanTasks::TaskGroups::RecurringLogicTaskGroup' }, :source => :task_group
19
+
16
20
  # in fact, the task has only one owner but Rails don't let you to
17
21
  # specify has_one relation though has_many relation
18
22
  has_many :owners, :through => :locks, :source => :resource, :source_type => 'User',
@@ -30,6 +34,7 @@ module ForemanTasks
30
34
  scoped_search :in => :owners, :on => :id, :complete_value => true, :rename => "owner.id", :ext_method => :search_by_owner
31
35
  scoped_search :in => :owners, :on => :login, :complete_value => true, :rename => "owner.login", :ext_method => :search_by_owner
32
36
  scoped_search :in => :owners, :on => :firstname, :complete_value => true, :rename => "owner.firstname", :ext_method => :search_by_owner
37
+ scoped_search :in => :task_groups, :on => :id, :complete_value => true, :rename => "task_group.id"
33
38
 
34
39
  scope :active, -> { where('foreman_tasks_tasks.state != ?', :stopped) }
35
40
  scope :running, -> { where("foreman_tasks_tasks.state NOT IN ('stopped', 'paused')") }
@@ -141,6 +146,11 @@ module ForemanTasks
141
146
  'ForemanTasks::Task'
142
147
  end
143
148
 
149
+ def add_missing_task_groups(groups)
150
+ groups = [groups] unless groups.is_a? Array
151
+ (groups - task_groups).each { |group| task_groups << group }
152
+ end
153
+
144
154
  protected
145
155
 
146
156
  def generate_id
@@ -0,0 +1,16 @@
1
+ module ForemanTasks
2
+ class TaskGroup < ActiveRecord::Base
3
+
4
+ has_many :task_group_members
5
+ has_many :tasks, :through => :task_group_members
6
+
7
+ def resource_name
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def resource
12
+ raise NotImplementedError
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ module ForemanTasks
2
+ class TaskGroupMember < ActiveRecord::Base
3
+
4
+ belongs_to :task_group
5
+ belongs_to :task
6
+
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ module ForemanTasks
2
+ module TaskGroups
3
+ class RecurringLogicTaskGroup < ::ForemanTasks::TaskGroup
4
+
5
+ has_one :recurring_logic, :foreign_key => :task_group_id
6
+
7
+ alias_method :resource, :recurring_logic
8
+
9
+ def resource_name
10
+ N_('Recurring logic')
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,99 @@
1
+ module ForemanTasks
2
+ class Triggering < ActiveRecord::Base
3
+
4
+ attr_accessor :start_at_raw, :start_before_raw, :max_iteration, :input_type,
5
+ :cronline, :days, :days_of_week, :time, :end_time_limited,
6
+ :end_time
7
+
8
+ before_save do
9
+ if future?
10
+ parse_start_at!
11
+ parse_start_before!
12
+ end
13
+ end
14
+
15
+ after_find do |triggering|
16
+ triggering.mode = triggering.mode.to_sym
17
+ end
18
+
19
+ ALLOWED_MODES = [:immediate, :future, :recurring]
20
+ ALLOWED_INPUT_TYPES = [:cronline, :monthly, :weekly, :daily, :hourly]
21
+
22
+ TIME_FORMAT = "%Y-%m-%d %H:%M"
23
+ TIME_REGEXP = /\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}\Z/
24
+ DAYS_REGEXP = /\A(\s*\d{1,2}\s*)(,\s*\d{1,2}\s*)*\Z/
25
+
26
+ has_one :recurring_logic, :foreign_key => :triggering_id
27
+
28
+ validates :mode, :inclusion => { :in => ALLOWED_MODES,
29
+ :message => _("%{value} is not allowed triggering mode") }
30
+ validates :input_type, :if => :recurring?,
31
+ :inclusion => { :in => ALLOWED_INPUT_TYPES,
32
+ :message => _("%{value} is not allowed input type") }
33
+ validates_format_of :start_at_raw, :with => TIME_REGEXP, :if => :future?,
34
+ :message => _("%{value} is wrong format")
35
+ validates_format_of :start_before_raw, :with => TIME_REGEXP, :if => :future?,
36
+ :message => _("%{value} is wrong format"), :allow_blank => true
37
+ validates_format_of :days, :with => DAYS_REGEXP,
38
+ :if => Proc.new { |t| t.recurring? && t.input_type == :monthly }
39
+ validate :correct_cronline, :if => Proc.new { |t| t.recurring? && t.input_type == :cronline }
40
+
41
+ def self.new_from_params(params = {})
42
+ self.new(params).tap do |targeting|
43
+ targeting.mode = params.fetch(:mode, :immediate).to_sym
44
+ targeting.input_type = params.fetch(:input_type, :cronline).to_sym
45
+ targeting.end_time_limited = params[:end_time_limited] == "true"
46
+ targeting.start_at_raw = Time.now.strftime(TIME_FORMAT)
47
+ end
48
+ end
49
+
50
+ def trigger(action, *args)
51
+ case mode
52
+ when :immediate
53
+ ::ForemanTasks.async_task action, *args
54
+ when :future
55
+ ::ForemanTasks.delay action,
56
+ delay_options,
57
+ *args
58
+ when :recurring
59
+ ::ForemanTasks::RecurringLogic.new_from_triggering(self)
60
+ .start(action, *args)
61
+ end
62
+ end
63
+
64
+ def delay_options
65
+ {
66
+ :start_at => start_at,
67
+ :start_before => start_before
68
+ }
69
+ end
70
+
71
+ def future?
72
+ mode == :future
73
+ end
74
+
75
+ def immediate?
76
+ mode == :immediate
77
+ end
78
+
79
+ def recurring?
80
+ mode == :recurring
81
+ end
82
+
83
+ def parse_start_at!
84
+ self.start_at ||= Time.strptime(start_at_raw, TIME_FORMAT)
85
+ end
86
+
87
+ def parse_start_before!
88
+ self.start_before ||= Time.strptime(start_before_raw, TIME_FORMAT) unless start_before_raw.blank?
89
+ end
90
+
91
+ private
92
+
93
+ def correct_cronline
94
+ ForemanTasks::RecurringLogic.new_from_cronline(cronline).next_occurrence_time
95
+ rescue ArgumentError => _
96
+ self.errors.add(:cronline, _("#{cronline} is not valid format of cronline"))
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,14 @@
1
+ <div class="form-group trigger_mode">
2
+ <% javascript 'trigger_form' %>
3
+ <%= javascript_tag do %>
4
+ $(function() { trigger_form_selector_binds('<%= f.options[:html][:id] %>','<%= f.object_name %>') });
5
+ <% end %>
6
+ <%= fields_for :triggering, triggering do |trigger_fields| %>
7
+ <legend><%= _('Schedule Job') %></legend>
8
+ <%= radio_button_f trigger_fields, :mode, :class => 'trigger_mode_selector', :value => 'immediate', :text => _("Execute now") %>
9
+ <%= radio_button_f trigger_fields, :mode, :class => 'trigger_mode_selector', :value => 'future', :text => _("Schedule future execution") %>
10
+ <%= radio_button_f trigger_fields, :mode, :class => 'trigger_mode_selector', :value => 'recurring', :text => _("Set up recurring execution") %>
11
+ <%= future_mode_fieldset trigger_fields, triggering %>
12
+ <%= recurring_mode_fieldset trigger_fields, triggering %>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <%= render 'foreman_tasks/task_groups/tab_related', :source => source %>
3
+ </div>
@@ -0,0 +1,34 @@
1
+ <% title _("Recurring logics") %>
2
+ <% title_actions help_path %>
3
+
4
+ <table class="table table-fixed table-bordered table-striped table-condensed">
5
+ <thead>
6
+ <th><%= N_("Cronline") %></th>
7
+ <th><%= N_("Task count") %></th>
8
+ <th><%= N_("Action") %></th>
9
+ <th><%= N_("Last occurrence") %></th>
10
+ <th><%= N_("Next occurrence") %></th>
11
+ <th><%= N_("Current iteration") %></th>
12
+ <th><%= N_("Iteration limit") %></th>
13
+ <th><%= N_("Repeat until") %></th>
14
+ <th><%= N_("State") %></th>
15
+ <th/>
16
+ </thead>
17
+ <% @recurring_logics.each do |recurring_logic| %>
18
+ <tr>
19
+ <td><%= link_to(recurring_logic.cron_line, foreman_tasks_recurring_logic_path(recurring_logic)) %></td>
20
+ <td><%= link_to(recurring_logic.tasks.count, foreman_tasks_tasks_url(:search => "task_group.id = #{recurring_logic.task_group.id}")) %></td>
21
+ <td><%= format_task_input(recurring_logic.tasks.first, true) %></td>
22
+ <td><%= recurring_logic.tasks.order(:started_at).where('started_at IS NOT NULL').last.try(:started_at) || N_("-") %></td>
23
+ <td><%= recurring_logic_next_occurrence recurring_logic %></td>
24
+ <td><%= recurring_logic.iteration %></td>
25
+ <td><%= format_recurring_logic_limit recurring_logic.max_iteration %></td>
26
+ <td><%= format_recurring_logic_limit recurring_logic.end_time.try(:in_time_zone) %></td>
27
+ <td><%= recurring_logic_state(recurring_logic) %></td>
28
+ <td><%= recurring_logic_action_buttons(recurring_logic) %></td>
29
+ </tr>
30
+ <% end %>
31
+ </table>
32
+
33
+ <%= page_entries_info @recurring_logics %>
34
+ <%= will_paginate @recurring_logics %>
@@ -0,0 +1,12 @@
1
+ <% title N_('Recurring logic') %>
2
+
3
+ <% title_actions(button_group(recurring_logic_action_buttons(@recurring_logic))) %>
4
+
5
+ <div class="tab-content col-md-6">
6
+ <legend><%= _('Details') %></legend>
7
+ <%= render @recurring_logic.task_group, :task_group => @recurring_logic.task_group %>
8
+ </div>
9
+
10
+ <div class="tab-content col-md-6">
11
+ <%= render 'tab_related', :source => @recurring_logic %>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <div>
2
+ <table class='table table-condensed'>
3
+ <tr>
4
+ <th><%= N_('ID') %></th>
5
+ <td><%= link_to(task_group.id, foreman_tasks_task_group_url(task_group)) %></td>
6
+ </tr>
7
+ <tr>
8
+ <th><%= N_('Task count') %></th>
9
+ <td><%= link_to(task_group.tasks.count, foreman_tasks_tasks_url(:search => "task_group.id = #{task_group.id}")) %></td>
10
+ </tr>
11
+ </table>
12
+ </div>
@@ -0,0 +1,7 @@
1
+ <legend><%= N_('Task group common') %></legend>
2
+ <%= render 'foreman_tasks/task_groups/common', :task_group => task_group %>
3
+ <legend><%= task_group.humanized_name %> specific</legend>
4
+ <div>
5
+ <%= begin; render task_group.to_partial_path.dup, :task_group => task_group; rescue ActionView::MissingTemplate => e; end %>
6
+ </div>
7
+
@@ -0,0 +1,17 @@
1
+ <div>
2
+ <legend><%= _("Associated resources") %></legend>
3
+ <ul>
4
+ <% source.task_groups.pluck(:type).uniq.each do |type| %>
5
+ <% next if type == source.task_group.type %>
6
+ <% groups = source.task_groups.where(:type => type) %>
7
+ <% begin %>
8
+ <%= render groups.first.to_partial_path.pluralize.dup, :source => source, :task_groups => groups %>
9
+ <% rescue ActionView::MissingTemplate => _ %>
10
+ <% groups.each do |group| %>
11
+ <%= render 'foreman_tasks/task_groups/common', :source => source, :task_group => group %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+ </ul>
16
+ </div>
17
+
@@ -0,0 +1,39 @@
1
+ <% recurring_logic = task_group.recurring_logic -%>
2
+ <table class='table table-condensed'>
3
+ <tr>
4
+ <th>ID</th>
5
+ <td><%= link_to(recurring_logic.id, recurring_logic) %></td>
6
+ </tr>
7
+ <tr>
8
+ <th><%= N_("Cronline") %></th>
9
+ <td><%= recurring_logic.cron_line %></td>
10
+ </tr>
11
+ <tr>
12
+ <th><%= N_("Action") %></th>
13
+ <td><%= format_task_input(recurring_logic.tasks.first, true) %></td>
14
+ </tr>
15
+ <tr>
16
+ <th><%= N_("Last occurrence") %></th>
17
+ <td><%= recurring_logic.tasks.order(:started_at).where('started_at IS NOT NULL').last.try(:started_at) || N_('-') %></td>
18
+ </tr>
19
+ <tr>
20
+ <th><%= N_("Next occurrence") %></th>
21
+ <td><%= recurring_logic_next_occurrence recurring_logic %></td>
22
+ </tr>
23
+ <tr>
24
+ <th><%= N_("Current iteration") %></th>
25
+ <td><%= recurring_logic.iteration %></td>
26
+ </tr>
27
+ <tr>
28
+ <th><%= N_("Iteration limit") %></th>
29
+ <td><%= format_recurring_logic_limit recurring_logic.max_iteration %></td>
30
+ </tr>
31
+ <tr>
32
+ <th><%= N_("Repeat until") %></th>
33
+ <td><%= format_recurring_logic_limit recurring_logic.end_time.try(:in_time_zone) %></td>
34
+ </tr>
35
+ <tr>
36
+ <th><%= N_("State") %></th>
37
+ <td><%= recurring_logic_state(recurring_logic) %></td>
38
+ </tr>
39
+ </table>
data/config/routes.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  Foreman::Application.routes.draw do
2
2
  namespace :foreman_tasks do
3
+ resources :recurring_logics, :only => [:index, :show] do
4
+ member do
5
+ post :cancel
6
+ end
7
+ end
8
+
3
9
  resources :tasks, :only => [:index, :show] do
4
10
  collection do
5
11
  get 'auto_complete_search'
@@ -0,0 +1,5 @@
1
+ class AddTaskTypeValueIndex < ActiveRecord::Migration
2
+ def change
3
+ add_index :foreman_tasks_tasks, [:type, :label]
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class CreateRecurringLogic < ActiveRecord::Migration
2
+ def change
3
+ create_table :foreman_tasks_recurring_logics do |t|
4
+ t.string :cron_line, :null => false
5
+ t.datetime :end_time
6
+ t.integer :max_iteration
7
+ t.integer :iteration, :default => 0
8
+ t.integer :task_group_id, :index => true, :null => false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ class CreateTaskGroups < ActiveRecord::Migration
2
+ def up
3
+ create_table :foreman_tasks_task_groups do |t|
4
+ t.string :type, index: true, null: false
5
+ end
6
+
7
+ create_table :foreman_tasks_task_group_members do |t|
8
+ t.string :task_id, null: false
9
+ t.integer :task_group_id, null: false
10
+ end
11
+
12
+ add_index :foreman_tasks_task_group_members, [:task_id, :task_group_id], unique: true, name: 'foreman_tasks_task_group_members_index'
13
+ end
14
+
15
+ def down
16
+ drop_table :foreman_tasks_task_groups
17
+ drop_table :foreman_tasks_task_group_members
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ class AddRecurringLogicState < ActiveRecord::Migration
2
+ def up
3
+ add_column :foreman_tasks_recurring_logics, :state, :string, :index => true
4
+ end
5
+
6
+ def down
7
+ remove_column :foreman_tasks_recurring_logics, :state
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ class CreateTriggerings < ActiveRecord::Migration
2
+ def up
3
+ create_table :foreman_tasks_triggerings do |t|
4
+ t.string :mode, null: false
5
+ t.datetime :start_at
6
+ t.datetime :start_before
7
+ end
8
+
9
+ change_table :foreman_tasks_recurring_logics do |t|
10
+ t.integer :triggering_id
11
+ end
12
+ end
13
+
14
+ def down
15
+ drop_table :foreman_tasks_triggerings
16
+ remove_column :foreman_tasks_recurring_logics, :triggering_id
17
+ end
18
+ end
@@ -15,11 +15,23 @@ module ForemanTasks
15
15
  :caption => N_('Tasks'),
16
16
  :parent => :monitor_menu
17
17
 
18
+ menu :top_menu, :recurring_logics, :after => :tasks,
19
+ :url_hash => { :controller => 'foreman_tasks/recurring_logics', :action => :index },
20
+ :caption => N_('Recurring logics'),
21
+ :parent => :monitor_menu
22
+
18
23
  security_block :foreman_tasks do |map|
19
24
  permission :view_foreman_tasks, {:'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :show],
20
25
  :'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary] }, :resource_type => ForemanTasks::Task.name
21
26
  permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step, :cancel],
22
27
  :'foreman_tasks/api/tasks' => [:bulk_resume]}, :resource_type => ForemanTasks::Task.name
28
+
29
+ permission :create_recurring_logics, { }, :resource_type => ForemanTasks::RecurringLogic
30
+
31
+ permission :view_recurring_logics, { :'foreman_tasks/recurring_logics' => [:index, :show] }, :resource_type => ForemanTasks::RecurringLogic
32
+
33
+ permission :edit_recurring_logics, { :'foreman_tasks/recurring_logics' => [:cancel] }, :resource_type => ForemanTasks::RecurringLogic
34
+
23
35
  end
24
36
 
25
37
  logger :dynflow, :enabled => true
@@ -27,7 +27,7 @@ namespace :foreman_tasks do
27
27
 
28
28
  def render_task(task)
29
29
  @plan = task.execution_plan
30
- erb('show', {})
30
+ erb('show')
31
31
  end
32
32
 
33
33
  def world
@@ -38,7 +38,7 @@ namespace :foreman_tasks do
38
38
  File.join(Gem::Specification.find_by_name("dynflow").gem_dir, 'web', 'views', "#{filename}.erb")
39
39
  end
40
40
 
41
- def erb(file, options)
41
+ def erb(file, options = {})
42
42
  unless @cache[file]
43
43
  @cache[file] = Tilt.new(template(file))
44
44
  end