foreman_remote_execution 0.1.1 → 0.1.2

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