foreman_remote_execution 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/template_invocation.js +48 -5
  3. data/app/controllers/api/v2/job_invocations_controller.rb +55 -10
  4. data/app/controllers/api/v2/job_templates_controller.rb +19 -4
  5. data/app/controllers/api/v2/template_inputs_controller.rb +88 -0
  6. data/app/controllers/job_invocations_controller.rb +17 -15
  7. data/app/controllers/template_invocations_controller.rb +2 -0
  8. data/app/helpers/remote_execution_helper.rb +27 -16
  9. data/app/lib/actions/middleware/bind_job_invocation.rb +7 -3
  10. data/app/lib/actions/remote_execution/run_host_job.rb +28 -17
  11. data/app/lib/actions/remote_execution/run_hosts_job.rb +9 -6
  12. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +1 -1
  13. data/app/models/concerns/foreman_remote_execution/foreman_tasks_triggering_extensions.rb +9 -0
  14. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +3 -1
  15. data/app/models/job_invocation.rb +48 -41
  16. data/app/models/job_invocation_composer.rb +205 -80
  17. data/app/models/job_invocation_task_group.rb +18 -0
  18. data/app/models/job_template.rb +25 -1
  19. data/app/models/job_template_effective_user.rb +23 -0
  20. data/app/models/remote_execution_provider.rb +25 -11
  21. data/app/models/setting/remote_execution.rb +6 -0
  22. data/app/models/ssh_execution_provider.rb +37 -0
  23. data/app/models/targeting.rb +13 -0
  24. data/app/models/template_input.rb +4 -1
  25. data/app/models/template_invocation.rb +23 -0
  26. data/app/views/api/v2/job_invocations/base.json.rabl +4 -0
  27. data/app/views/api/v2/job_invocations/index.json.rabl +1 -1
  28. data/app/views/api/v2/job_invocations/main.json.rabl +19 -0
  29. data/app/views/api/v2/job_invocations/show.json.rabl +0 -15
  30. data/app/views/api/v2/job_templates/base.json.rabl +1 -1
  31. data/app/views/api/v2/job_templates/index.json.rabl +1 -1
  32. data/app/views/api/v2/job_templates/main.json.rabl +5 -1
  33. data/app/views/api/v2/job_templates/show.json.rabl +4 -0
  34. data/app/views/api/v2/job_templates/update.json.rabl +3 -0
  35. data/app/views/api/v2/template_inputs/base.json.rabl +3 -0
  36. data/app/views/api/v2/template_inputs/create.json.rabl +3 -0
  37. data/app/views/api/v2/template_inputs/index.json.rabl +3 -0
  38. data/app/views/api/v2/template_inputs/main.json.rabl +9 -0
  39. data/app/views/api/v2/template_inputs/show.json.rabl +3 -0
  40. data/app/views/job_invocation_task_groups/_job_invocation_task_group.html.erb +31 -0
  41. data/app/views/job_invocation_task_groups/_job_invocation_task_groups.html.erb +3 -0
  42. data/app/views/job_invocations/_form.html.erb +102 -71
  43. data/app/views/job_invocations/_tab_overview.html.erb +5 -2
  44. data/app/views/job_invocations/index.html.erb +4 -4
  45. data/app/views/job_invocations/refresh.js.erb +2 -1
  46. data/app/views/job_invocations/show.html.erb +13 -2
  47. data/app/views/job_invocations/show.js.erb +1 -1
  48. data/app/views/job_templates/_custom_tabs.html.erb +16 -0
  49. data/app/views/templates/package_action.erb +1 -0
  50. data/app/views/templates/puppet_run_once.erb +1 -0
  51. data/app/views/templates/run_command.erb +1 -0
  52. data/app/views/templates/service_action.erb +1 -0
  53. data/config/routes.rb +15 -2
  54. data/db/migrate/20150923125825_add_job_invocation_task_group.rb +10 -0
  55. data/db/migrate/20151022105508_rename_last_task_id_column.rb +6 -0
  56. data/db/migrate/20151116105412_add_triggering_to_job_invocation.rb +10 -0
  57. data/db/migrate/20151120171100_add_effective_user_to_template_invocation.rb +5 -0
  58. data/db/migrate/20151124162300_create_job_template_effective_users.rb +13 -0
  59. data/db/migrate/20151203100824_add_description_to_job_invocation.rb +11 -0
  60. data/db/migrate/20151215114631_add_host_id_to_template_invocation.rb +29 -0
  61. data/db/migrate/20151217092555_migrate_to_task_groups.rb +16 -0
  62. data/foreman_remote_execution.gemspec +2 -1
  63. data/lib/foreman_remote_execution/engine.rb +30 -5
  64. data/lib/foreman_remote_execution/version.rb +1 -1
  65. data/test/factories/foreman_remote_execution_factories.rb +5 -0
  66. data/test/functional/api/v2/job_invocations_controller_test.rb +3 -3
  67. data/test/functional/api/v2/template_inputs_controller_test.rb +61 -0
  68. data/test/unit/actions/run_hosts_job_test.rb +10 -3
  69. data/test/unit/concerns/host_extensions_test.rb +10 -6
  70. data/test/unit/job_invocation_composer_test.rb +229 -10
  71. data/test/unit/job_invocation_test.rb +27 -27
  72. data/test/unit/job_template_effective_user_test.rb +41 -0
  73. data/test/unit/job_template_test.rb +24 -0
  74. data/test/unit/remote_execution_provider_test.rb +39 -0
  75. metadata +42 -7
  76. data/app/models/job_invocation_api_composer.rb +0 -69
  77. data/test/unit/job_invocation_api_composer_test.rb +0 -143
@@ -17,6 +17,12 @@ class Setting::RemoteExecution < Setting
17
17
  self.set('remote_execution_ssh_user',
18
18
  N_("Default user to use for SSH. You may override per host by setting a parameter called remote_execution_ssh_user."),
19
19
  'root'),
20
+ self.set('remote_execution_effective_user',
21
+ N_("Default user to use for executing the script. If the user differs from the SSH user, su or sudo is used to switch the user."),
22
+ 'root'),
23
+ self.set('remote_execution_effective_user_method',
24
+ N_("What command should be used to switch to the effective user. One of %s") % SSHExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
25
+ 'sudo')
20
26
  ].each { |s| self.create! s.update(:category => "Setting::RemoteExecution") }
21
27
  end
22
28
 
@@ -1,2 +1,39 @@
1
1
  class SSHExecutionProvider < RemoteExecutionProvider
2
+
3
+ EFFECTIVE_USER_METHODS = %w[sudo su]
4
+
5
+ class << self
6
+ def proxy_command_options(template_invocation, host)
7
+ super.merge(:ssh_user => ssh_user(host),
8
+ :effective_user => effective_user(template_invocation),
9
+ :effective_user_method => effective_user_method(host))
10
+ end
11
+
12
+ def humanized_name
13
+ _("Ssh")
14
+ end
15
+
16
+ def supports_effective_user?
17
+ true
18
+ end
19
+
20
+ private
21
+
22
+ def ssh_user(host)
23
+ host.params['remote_execution_ssh_user']
24
+ end
25
+
26
+ def effective_user(template_invocation)
27
+ template_invocation.effective_user
28
+ end
29
+
30
+ def effective_user_method(host)
31
+ method = host.params['remote_execution_effective_user_method'] || Setting[:remote_execution_effective_user_method]
32
+ unless EFFECTIVE_USER_METHODS.include?(method)
33
+ raise _('Effective user method "%{current_value}" is not one of %{valid_methods}') %
34
+ { :current_value => method, :valid_methods => EFFECTIVE_USER_METHODS}
35
+ end
36
+ method
37
+ end
38
+ end
2
39
  end
@@ -20,6 +20,19 @@ class Targeting < ActiveRecord::Base
20
20
 
21
21
  before_create :assign_search_query, :if => :static?
22
22
 
23
+ def clone
24
+ if static?
25
+ self.dup
26
+ else
27
+ Targeting.new(
28
+ :user => self.user,
29
+ :bookmark_id => self.bookmark.try(:id),
30
+ :targeting_type => self.targeting_type,
31
+ :search_query => self.search_query
32
+ )
33
+ end.tap(&:save)
34
+ end
35
+
23
36
  def resolve_hosts!
24
37
  raise ::Foreman::Exception, _('Cannot resolve hosts without a user') if user.nil?
25
38
  raise ::Foreman::Exception, _('Cannot resolve hosts without a bookmark or search query') if bookmark.nil? && search_query.blank?
@@ -8,12 +8,15 @@ class TemplateInput < ActiveRecord::Base
8
8
  :puppet_parameter => N_('Puppet parameter') }.with_indifferent_access
9
9
 
10
10
  attr_accessible :name, :required, :input_type, :fact_name, :variable_name,
11
- :puppet_class_name, :puppet_parameter_name, :description, :job_template_id,
11
+ :puppet_class_name, :puppet_parameter_name, :description, :template_id,
12
12
  :options
13
13
 
14
14
  belongs_to :template
15
15
  has_many :template_invocation_input_values, :dependent => :destroy
16
16
 
17
+ scoped_search :on => :name, :complete_value => true
18
+ scoped_search :on => :input_type, :complete_value => true
19
+
17
20
  validates :name, :presence => true, :uniqueness => { :scope => 'template_id' }
18
21
  validates :input_type, :presence => true, :inclusion => TemplateInput::TYPES.keys
19
22
 
@@ -1,4 +1,5 @@
1
1
  class TemplateInvocation < ActiveRecord::Base
2
+ include Authorizable
2
3
  include ForemanTasks::Concerns::ActionSubject
3
4
 
4
5
  include ForemanRemoteExecution::ErrorsFlattener
@@ -11,14 +12,28 @@ class TemplateInvocation < ActiveRecord::Base
11
12
  belongs_to :job_invocation, :inverse_of => :template_invocations
12
13
  has_many :input_values, :class_name => 'TemplateInvocationInputValue', :dependent => :destroy
13
14
  has_one :targeting, :through => :job_invocation
15
+ belongs_to :host, :class_name => 'Host::Managed', :foreign_key => :host_id
16
+ has_one :host_group, :through => :host, :source => :hostgroup
14
17
 
15
18
  validates_associated :input_values
16
19
  validate :provides_required_input_values
20
+ before_validation :set_effective_user
21
+
22
+ scoped_search :in => :host, :on => :name, :rename => 'host.name', :complete_value => true
23
+ scoped_search :in => :host_group, :on => :name, :rename => 'host_group.name', :complete_value => true
24
+ scoped_search :in => :template, :on => :job_name, :complete_value => true
17
25
 
18
26
  def to_action_input
19
27
  { :id => id, :name => template.name }
20
28
  end
21
29
 
30
+ def deep_clone
31
+ self.dup.tap do |invocation|
32
+ invocation.input_values = self.input_values.map(&:dup)
33
+ invocation.input_values.each(&:save!)
34
+ end
35
+ end
36
+
22
37
  private
23
38
 
24
39
  def provides_required_input_values
@@ -28,4 +43,12 @@ class TemplateInvocation < ActiveRecord::Base
28
43
  errors.add(:base, _("Not all required inputs have values. Missing inputs: %s") % missing_inputs.map(&:name).join(', '))
29
44
  end
30
45
  end
46
+
47
+ def set_effective_user
48
+ if template.provider.supports_effective_user?
49
+ self.effective_user = Setting[:remote_execution_effective_user] if self.effective_user.blank?
50
+ else
51
+ self.effective_user = nil
52
+ end
53
+ end
31
54
  end
@@ -1,3 +1,7 @@
1
1
  object @job_invocation
2
2
 
3
3
  attributes :id, :job_name, :targeting_id
4
+
5
+ child :task do
6
+ attributes :id, :state
7
+ end
@@ -1,3 +1,3 @@
1
1
  collection @job_invocations
2
2
 
3
- extends "api/v2/job_invocations/main"
3
+ extends "api/v2/job_invocations/base"
@@ -1,3 +1,22 @@
1
1
  object @job_invocation
2
2
 
3
3
  extends "api/v2/job_invocations/base"
4
+
5
+ child :targeting do
6
+ attributes :bookmark_id, :search_query, :targeting_type, :user_id
7
+
8
+ child :hosts do
9
+ extends "api/v2/hosts/base"
10
+ end
11
+ end
12
+
13
+ child :task do
14
+ attributes :id, :state
15
+ end
16
+
17
+ child :template_invocations do
18
+ attributes :template_id, :template_name
19
+ child :input_values do
20
+ attributes :template_input_name, :template_input_id, :value
21
+ end
22
+ end
@@ -1,18 +1,3 @@
1
1
  object @job_invocation
2
2
 
3
3
  extends "api/v2/job_invocations/main"
4
-
5
- child :targeting do
6
- attributes :bookmark_id, :search_query, :targeting_type, :user_id
7
- end
8
-
9
- child :last_task do
10
- attributes :id, :state
11
- end
12
-
13
- child :template_invocations do
14
- attributes :template_id
15
- child :input_values do
16
- attributes :template_input_id, :value
17
- end
18
- end
@@ -1,3 +1,3 @@
1
1
  object @job_template
2
2
 
3
- attributes :id, :name, :job_name, :provider_type
3
+ attributes :id, :name, :job_name, :provider_type, :snippet
@@ -1,3 +1,3 @@
1
1
  collection @job_templates
2
2
 
3
- extends "api/v2/job_templates/main"
3
+ extends "api/v2/job_templates/base"
@@ -2,4 +2,8 @@ object @job_template
2
2
 
3
3
  extends "api/v2/job_templates/base"
4
4
 
5
- attributes :snippet, :audit_comment, :created_at, :updated_at
5
+ attributes :audit_comment, :created_at, :updated_at
6
+
7
+ child :template_inputs do
8
+ extends "api/v2/template_inputs/base"
9
+ end
@@ -4,6 +4,10 @@ extends "api/v2/job_templates/main"
4
4
 
5
5
  attributes :template, :locked
6
6
 
7
+ child :effective_user => :effective_user do
8
+ attributes :value, :current_user, :overridable
9
+ end
10
+
7
11
  node do |job_template|
8
12
  partial("api/v2/taxonomies/children_nodes", :object => job_template)
9
13
  end
@@ -0,0 +1,3 @@
1
+ object @job_template
2
+
3
+ extends "api/v2/job_templates/show"
@@ -0,0 +1,3 @@
1
+ object @template_input
2
+
3
+ attributes :id, :name, :input_type
@@ -0,0 +1,3 @@
1
+ object @template_input
2
+
3
+ extends "api/v2/template_inputs/show"
@@ -0,0 +1,3 @@
1
+ collection @template_inputs
2
+
3
+ extends "api/v2/template_inputs/main"
@@ -0,0 +1,9 @@
1
+ object @template_input
2
+
3
+ extends "api/v2/template_inputs/base"
4
+
5
+ attributes :template_id, :fact_name, :variable_name, :puppet_parameter_name
6
+
7
+ node :options do |input|
8
+ input.options.split(/\r?\n/) if input.options.present?
9
+ end
@@ -0,0 +1,3 @@
1
+ object @template_input
2
+
3
+ extends "api/v2/template_inputs/main"
@@ -0,0 +1,31 @@
1
+ <% job_invocation = task_group.job_invocation %>
2
+ <table class='table table-condensed'>
3
+ <tr>
4
+ <th>ID</th>
5
+ <td><%= link_to(job_invocation.id, job_invocation) %></td>
6
+ </tr>
7
+ <tr>
8
+ <th>Job Name</th>
9
+ <td><%= job_invocation.job_name %></td>
10
+ </tr>
11
+ <tr>
12
+ <th>Status</th>
13
+ <td><%= link_to_invocation_task_if_authorized(job_invocation) %></td>
14
+ </tr>
15
+ <tr>
16
+ <th>Succeeded</th>
17
+ <td><%= invocation_count(job_invocation, :output_key => :success_count) %></td>
18
+ </tr>
19
+ <tr>
20
+ <th>Failed</th>
21
+ <td><%= invocation_count(job_invocation, :output_key => :failed_count) %></td>
22
+ </tr>
23
+ <tr>
24
+ <th>Pending</th>
25
+ <td><%= invocation_count(job_invocation, :output_key => :pending_count) %></td>
26
+ </tr>
27
+ <tr>
28
+ <th>Total hosts</th>
29
+ <td><%= invocation_count(job_invocation, :output_key => :total_count) %></td>
30
+ </tr>
31
+ </table>
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to("#{task_groups.first.resource_name}: #{task_groups.count}x", job_invocations_path(:search => task_groups.first.class.search_query_for(source))) %>
3
+ </li>
@@ -1,88 +1,119 @@
1
- <%= form_for @composer.job_invocation, :html => { 'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form' } do |f| %>
2
- <%= selectable_f f, :job_name, @composer.available_job_names %>
1
+ <script type="text/javascript">
2
+ $(function () {
3
+ regenerate_description($('#description'), true)
4
+ });
5
+ </script>
3
6
 
4
- <%= fields_for @composer.targeting do |targeting_fields| %>
5
- <span id="bookmark_query_map" >
6
- <% @composer.available_bookmarks.each do |bookmark| %>
7
- <span id="bookmark-<%= bookmark.id %>" data-query="<%= bookmark.query %>"></span>
8
- <% end %>
9
- </span>
10
- <%= selectable_f targeting_fields, :bookmark_id, @composer.available_bookmarks.map {|b| [ b.name, b.id ] }, :selected => @composer.targeting.bookmark_id, :include_blank => true %>
11
- <%= textarea_f targeting_fields, :search_query, :value => @composer.displayed_search_query, :rows => 5 %>
7
+ <%= form_for @composer.job_invocation, :html => {'data-refresh-url' => refresh_job_invocations_path, :id => 'job_invocation_form'} do |f| %>
12
8
 
13
- <div class="form-group ">
14
- <label class="col-md-2 control-label"><%= _('Resolves to') %></label>
15
- <div class="col-md-4">
16
- <%= @composer.targeted_hosts_count %> <%= _('hosts') %>
17
- <%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Refresh"), :id => 'refresh_execution_form') do %>
18
- <%= icon_text('refresh') %>
19
- <% end %>
20
- <%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Preview"), :id => 'preview_hosts') do %>
21
- <%= icon_text('eye-open') %>
22
- <% end %>
23
- </div>
24
- </div>
9
+ <ul class="nav nav-tabs" data-tabs="tabs">
10
+ <li class="active"><a href="#primary" data-toggle="tab"><%= _('Job') %></a></li>
11
+ <li><a href="#scheduling" data-toggle="tab">Scheduling</a></li>
12
+ </ul>
25
13
 
26
- <div class="form-group ">
27
- <label class="col-md-2 control-label"><%= _('Type of query') %></label>
28
- <div class="col-md-4">
29
- <%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
30
- <%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::DYNAMIC_TYPE, :text => _(Targeting::TYPES[Targeting::DYNAMIC_TYPE]) %>
31
- </div>
32
- <span class="help-inline"><%= popover(_('Explanation'),
33
- _("Type has impact on when is the query evaulated to hosts.<br><ul><li><b>Static</b> - evaluates just after you submit this form</li><li><b>Dynamic</b> - evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it</li></ul>")) %></span>
34
- </div>
35
- <% end %>
14
+ <div class="tab-content">
15
+ <div class="tab-pane active" id="primary">
16
+ <%= selectable_f f, :job_name, @composer.available_job_names %>
17
+
18
+ <%= fields_for @composer.targeting do |targeting_fields| %>
19
+ <span id="bookmark_query_map">
20
+ <% @composer.available_bookmarks.each do |bookmark| %>
21
+ <span id="bookmark-<%= bookmark.id %>" data-query="<%= bookmark.query %>"></span>
22
+ <% end %>
23
+ </span>
24
+ <%= selectable_f targeting_fields, :bookmark_id, @composer.available_bookmarks.map { |b| [b.name, b.id] }, :selected => @composer.targeting.bookmark_id, :include_blank => true %>
25
+ <%= textarea_f targeting_fields, :search_query, :value => @composer.displayed_search_query, :rows => 5 %>
36
26
 
37
- <% @composer.displayed_provider_types.each do |provider_type| %>
38
- <fieldset id="provider_<%= provider_type %>" class="provider_form">
39
- <legend><%= _('Provider') + ': ' + provider_type %></legend>
40
- <%= f.fields_for 'providers' do |providers_fields| %>
41
- <%= providers_fields.fields_for provider_type do |provider_type_fields| %>
42
- <%= provider_type_fields.fields_for :job_templates do |job_templates_fields| %>
43
- <% if @composer.needs_provider_type_selection? %>
44
- <%= radio_button_f provider_type_fields, 'job_template_id', :value => '', :text => _('Disable this provider'),
45
- :class => 'job_template_selector', :checked => @composer.preselect_disabled_for_provider(provider_type) %>
46
- <span />
27
+ <div class="form-group ">
28
+ <label class="col-md-2 control-label"><%= _('Resolves to') %></label>
29
+
30
+ <div class="col-md-4">
31
+ <%= @composer.targeted_hosts_count %> <%= _('hosts') %>
32
+ <%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Refresh"), :id => 'refresh_execution_form') do %>
33
+ <%= icon_text('refresh') %>
34
+ <% end %>
35
+ <%= button_tag(:type => 'button', :class => 'btn btn-default btn-sm', :title => _("Preview"), :id => 'preview_hosts') do %>
36
+ <%= icon_text('eye-open') %>
47
37
  <% end %>
48
- <% @composer.templates_for_provider(provider_type).each do |job_template| %>
49
- <%= radio_button_f provider_type_fields, 'job_template_id',
50
- :value => job_template.id,
51
- :text => job_template.name,
52
- :class => 'job_template_selector',
53
- :checked => @composer.job_template_ids.include?(job_template.id) || @composer.only_one_template_available? %>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="form-group ">
42
+ <label class="col-md-2 control-label"><%= _('Type of query') %></label>
43
+
44
+ <div class="col-md-4">
45
+ <%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::STATIC_TYPE, :text => _(Targeting::TYPES[Targeting::STATIC_TYPE]) %>
46
+ <%= radio_button_f targeting_fields, :targeting_type, :value => Targeting::DYNAMIC_TYPE, :text => _(Targeting::TYPES[Targeting::DYNAMIC_TYPE]) %>
47
+ </div>
48
+ <span class="help-inline"><%= popover(_('Explanation'),
49
+ _("Type has impact on when is the query evaulated to hosts.<br><ul><li><b>Static</b> - evaluates just after you submit this form</li><li><b>Dynamic</b> - evaluates just before the execution is started, so if it's planed in future, targeted hosts set may change before it</li></ul>")) %></span>
50
+ </div>
51
+ <% end %>
54
52
 
55
- <fieldset id="job_template_<%= job_template.id %>" class="job_template_form <%= 'hidden' if !@composer.job_template_ids.include?(job_template.id) && !@composer.only_one_template_available? %>">
56
- <%= job_templates_fields.fields_for job_template.id.to_s do |job_template_fields| %>
57
- <%= job_template_fields.fields_for :input_values do |input_values_fields| %>
58
- <% job_template.template_inputs.where(:input_type => 'user').each do |input| %>
59
- <%= input_values_fields.fields_for input.id.to_s, @composer.template_invocation_input_value_for(input) do |input_fields| %>
60
- <% unless input.options.blank? %>
61
- <%= selectable_f input_fields, :value, input.options_array, {:include_blank => !input.required }, :require => input.required, :label => input.name, :help_inline => input.description %>
62
- <% else %>
63
- <%= textarea_f input_fields, :value, :label => input.name, :help_inline => input.description, :required => input.required, :rows => 2 %>
53
+ <% @composer.displayed_provider_types.each do |provider_type| %>
54
+ <fieldset id="provider_<%= provider_type %>" class="provider_form">
55
+ <legend><%= _('Provider') + ': ' + provider_type %></legend>
56
+ <%= f.fields_for 'providers' do |providers_fields| %>
57
+ <%= providers_fields.fields_for provider_type do |provider_type_fields| %>
58
+ <%= provider_type_fields.fields_for :job_templates do |job_templates_fields| %>
59
+ <% if @composer.needs_provider_type_selection? %>
60
+ <%= radio_button_f provider_type_fields, 'job_template_id', :value => '', :text => _('Disable this provider'),
61
+ :class => 'job_template_selector', :checked => @composer.preselect_disabled_for_provider(provider_type) %>
62
+ <span/>
63
+ <% end %>
64
+ <% @composer.templates_for_provider(provider_type).each do |job_template| %>
65
+ <%= radio_button_f provider_type_fields, 'job_template_id',
66
+ :value => job_template.id,
67
+ :text => job_template.name,
68
+ :onchange => "regenerate_description($(this).parent().next('fieldset'));",
69
+ :class => 'job_template_selector',
70
+ :checked => @composer.job_template_ids.include?(job_template.id) || @composer.only_one_template_available? %>
71
+
72
+ <fieldset id="job_template_<%= job_template.id %>" class="job_template_form <%= 'hidden' if !@composer.job_template_ids.include?(job_template.id) && !@composer.only_one_template_available? %>">
73
+ <%= job_templates_fields.fields_for job_template.id.to_s do |job_template_fields| %>
74
+
75
+ <%= job_template_fields.fields_for :input_values do |input_values_fields| %>
76
+ <% job_template.template_inputs.where(:input_type => 'user').each do |input| %>
77
+ <%= input_values_fields.fields_for input.id.to_s, @composer.template_invocation_input_value_for(input) do |input_fields| %>
78
+ <% unless input.options.blank? %>
79
+ <%= selectable_f input_fields, :value, input.options_array, {:include_blank => !input.required}, :require => input.required, :label => input.name, :help_inline => input.description, :id => input.name, :onchange => "regenerate_description(this);" %>
80
+ <% else %>
81
+ <%= textarea_f input_fields, :value, :label => input.name, :help_inline => input.description, :required => input.required, :rows => 2, :onchange => "regenerate_description(this);", :id => input.name %>
82
+ <% end %>
83
+ <% end %>
64
84
  <% end %>
65
85
  <% end %>
86
+ <% if job_template.effective_user.overridable? %>
87
+ <%= text_f job_template_fields, :effective_user, :label => _('Effective user'), :help_inline => N_("A user to be used for executing the script. If it differs from the SSH user, su or sudo is used to switch the accounts.") %>
88
+ <% end %>
89
+
66
90
  <% end %>
67
- <% end %>
91
+ <%= textarea_f f, :description, :label => _("Description"), :readonly => "true", :id => "description", :rows => 2, :help_inline => content_tag(:span, check_box_tag('description_format_override', job_template.generate_description_format, true, :onchange => "description_override(this);") + ' ' + _("Use default description template")) %>
92
+ <div id="description_format_container" class="hidden">
93
+ <%= textarea_f f, :description_format,
94
+ :label => _("Description template"),
95
+ :value => job_template.generate_description_format,
96
+ :rows => 2,
97
+ :onchange => "regenerate_description(this);",
98
+ :id => "description_format",
99
+ :help_inline => popover(_('Explanation'), _('Description template determines the job name once it is submitted. Input values can become part of the name
100
+ if they are specified using interpolation syntax, e.g. %{fqdn} where fqdn is the name of interpolated input.')) %>
101
+ </div>
102
+ </fieldset>
68
103
  <% end %>
69
- </fieldset>
104
+ <% end %>
70
105
  <% end %>
71
106
  <% end %>
72
- <% end %>
107
+ </fieldset>
73
108
  <% end %>
74
- </fieldset>
75
- <% end %>
76
109
 
77
- <div class="form-group trigger_mode">
78
- <legend><%= _('Schedule Job') %></legend>
79
- <%= radio_button_f f, :trigger_mode, :value => 'immediate', :text => _("Run Job Now"), :class => 'trigger_mode_selector' %>
80
- <%= radio_button_f f, :trigger_mode, :value => 'future', :text => _("Schedule Future Job"), :class => 'trigger_mode_selector' %>
81
- <fieldset id="trigger_mode_future" class="trigger_mode_form <%= 'hidden' if @composer.job_invocation.trigger_mode != :future %>">
82
- <%= text_f f, :start_at, :label => _('Start at') %>
83
- <%= text_f f, :start_before, :label => _('Start before') %>
84
- </fieldset>
110
+ <%= render :partial => 'preview_hosts_modal' %>
111
+ </div>
112
+
113
+ <div class="tab-pane" id="scheduling">
114
+ <%= trigger_selector f, @composer.triggering %>
115
+ </div>
85
116
  </div>
86
- <%= render :partial => 'preview_hosts_modal' %>
117
+
87
118
  <%= submit_or_cancel f %>
88
119
  <% end %>
@@ -15,7 +15,7 @@
15
15
  <%= _('Evaluated at:') %> <%= job_invocation.targeting.resolved_at %><br>
16
16
  <% if job_invocation.template_invocations.size > 1 %>
17
17
  <% job_invocation.template_invocations.each do |template_invocation| %>
18
- <%= host_counter _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)), ForemanTasks::Task::DynflowTask.for_action(Actions::RemoteExecution::RunHostJob).for_resource(template_invocation).uniq.size %>
18
+ <%= host_counter template_invocation.template.provider.humanized_name, ForemanTasks::Task::DynflowTask.for_action(Actions::RemoteExecution::RunHostJob).for_resource(template_invocation).uniq.size %>
19
19
  <% end %>
20
20
  <% end %>
21
21
  <%= host_counter(_('Total hosts'), job_invocation.total_hosts_count) %>
@@ -25,7 +25,7 @@
25
25
  <h4><%= _('Providers and templates') %></h4>
26
26
  <% job_invocation.template_invocations.each do |template_invocation| %>
27
27
  <h5>
28
- <b><%= template_invocation.template.name %></b> <%= 'through' %> <%= _(RemoteExecutionProvider.provider_for(template_invocation.template.provider_type)) %>
28
+ <b><%= template_invocation.template.name %></b> <%= 'through' %> <%= template_invocation.template.provider.humanized_name %>
29
29
  </h5>
30
30
  <% target = template_invocation.targeting.hosts.with_os.first || template_invocation.targeting.hosts.first %>
31
31
  <%= _('Preview for target %s') % target.try(:name) || 'N/A' %>
@@ -40,5 +40,8 @@
40
40
  <% end %>
41
41
  </ul>
42
42
  <% end %>
43
+ <% if template_invocation.effective_user %>
44
+ <b><%= _("Effective user") %></b>: <%= template_invocation.effective_user %>
45
+ <% end %>
43
46
  <% end %>
44
47
  </div>
@@ -3,28 +3,28 @@
3
3
  <table class="table table-bordered table-striped table-condensed">
4
4
  <thead>
5
5
  <tr>
6
- <th><%= _('Job name') %></th>
6
+ <th><%= sort :job_name, :as => _('Job name') %></th>
7
7
  <th><%= _('Status') %></th>
8
8
  <th><%= _('Succeeded') %></th>
9
9
  <th><%= _('Failed') %></th>
10
10
  <th><%= _('Pending') %></th>
11
11
  <th><%= _('Total hosts') %></th>
12
+ <th><%= sort :started_at, :as => _('Started') %></th>
12
13
  </tr>
13
14
  </thead>
14
15
 
15
16
  <tbody>
16
17
  <% @job_invocations.each do |invocation| %>
17
18
  <tr>
18
- <td><%= link_to_if_authorized invocation.job_name, hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocation) %></td>
19
+ <td><%= link_to_if_authorized "#{invocation.description.try(:capitalize) || invocation.job_name}", hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocation) %></td>
19
20
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
20
21
  <td><%= invocation_count(invocation, :output_key => :success_count) %></td>
21
22
  <td><%= invocation_count(invocation, :output_key => :failed_count) %></td>
22
23
  <td><%= invocation_count(invocation, :output_key => :pending_count) %></td>
23
24
  <td><%= invocation_count(invocation, :output_key => :total_count) %></td>
25
+ <td><%= time_ago(invocation.task.try(:started_at)) %></td>
24
26
  </tr>
25
27
  <% end %>
26
28
  </tbody>
27
-
28
29
  </table>
29
-
30
30
  <%= will_paginate_with_info @job_invocations %>
@@ -1 +1,2 @@
1
- $('form#job_invocation_form').html("<%=j render :partial => 'form' %>");
1
+ $('form#job_invocation_form').replaceWith("<%=j render :partial => 'form' %>");
2
+ $('#job_invocation_form').find('a[rel="popover"]').popover();
@@ -2,13 +2,14 @@
2
2
  <% stylesheet 'job_invocations' %>
3
3
  <% javascript 'template_invocation' %>
4
4
 
5
- <% if @job_invocation.last_task %>
6
- <% title_actions(button_group(job_invocation_task_buttons(@job_invocation.last_task))) %>
5
+ <% if @job_invocation.task %>
6
+ <% title_actions(button_group(job_invocation_task_buttons(@job_invocation.task))) %>
7
7
  <% end %>
8
8
 
9
9
  <ul class="nav nav-tabs" data-tabs="tabs">
10
10
  <li class="<%= job_invocation_active_tab(:overview, params) %>"><a href="#primary" data-toggle="tab"><%= _('Overview') %></a></li>
11
11
  <li class="<%= job_invocation_active_tab(:hosts, params) %>"><a href="#hosts" data-toggle="tab"><%= _('Hosts') %></a></li>
12
+ <% unless @job_invocation.recurring_logic.nil? %><li><a href="#recurring_logic" data-toggle="tab"><%= _('Recurring logic') %></a></li><% end %>
12
13
  </ul>
13
14
 
14
15
  <div class="tab-content">
@@ -19,6 +20,16 @@
19
20
  <div class="tab-pane <%= job_invocation_active_tab(:hosts, params) %>" id="hosts" data-refresh_required="<%= @job_invocation.resolved? ? '' : 'true' %>">
20
21
  <%= render 'tab_hosts', :job_invocation => @job_invocation, :hosts => @hosts %>
21
22
  </div>
23
+
24
+ <% unless @job_invocation.recurring_logic.nil? %>
25
+ <div class="tab-pane" id="recurring_logic">
26
+ <div class='col-md-6'>
27
+ <% if @job_invocation.recurring_logic.try(:task_group) %>
28
+ <%= render @job_invocation.recurring_logic.task_group, :task_group => @job_invocation.recurring_logic.try(:task_group) %>
29
+ <% end %>
30
+ </div>
31
+ </div>
32
+ <% end %>
22
33
  </div>
23
34
 
24
35
  <script id="job_invocation_refresh" data-refresh-url="<%= job_invocation_path(@job_invocation) %>">
@@ -1,4 +1,4 @@
1
- $('div.btn-group').html('<%= button_group(job_invocation_task_buttons(@job_invocation.last_task)).html_safe %>');
1
+ $('div.btn-group').html('<%= button_group(job_invocation_task_buttons(@job_invocation.task)).html_safe %>');
2
2
  $('div#status_chart').html('<%=j job_invocation_chart(@job_invocation) %>');
3
3
  $('div#status').flot_pie();
4
4