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
@@ -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