foreman_remote_execution 3.3.6 → 4.2.1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/app/controllers/api/v2/job_invocations_controller.rb +20 -2
  4. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb +26 -0
  5. data/app/controllers/foreman_remote_execution/concerns/api/v2/subnets_controller_extensions.rb +21 -0
  6. data/app/controllers/job_invocations_controller.rb +22 -8
  7. data/app/controllers/job_templates_controller.rb +1 -1
  8. data/app/helpers/job_invocations_helper.rb +3 -2
  9. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  10. data/app/lib/actions/remote_execution/run_hosts_job.rb +12 -3
  11. data/app/lib/foreman_remote_execution/renderer/scope/input.rb +35 -0
  12. data/app/models/concerns/api/v2/interfaces_controller_extensions.rb +13 -0
  13. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +38 -14
  14. data/app/models/job_invocation.rb +12 -4
  15. data/app/models/job_invocation_composer.rb +3 -3
  16. data/app/models/remote_execution_feature.rb +5 -2
  17. data/app/models/remote_execution_provider.rb +8 -3
  18. data/app/models/setting/remote_execution.rb +2 -2
  19. data/app/models/ssh_execution_provider.rb +1 -1
  20. data/app/services/remote_execution_proxy_selector.rb +3 -0
  21. data/app/views/api/v2/interfaces/execution_flag.json.rabl +1 -0
  22. data/app/views/api/v2/job_invocations/base.json.rabl +1 -0
  23. data/app/views/api/v2/job_invocations/main.json.rabl +1 -1
  24. data/app/views/api/v2/registration/_form.html.erb +12 -0
  25. data/app/views/api/v2/subnets/remote_execution_proxies.json.rabl +3 -0
  26. data/app/views/job_invocations/_form.html.erb +1 -1
  27. data/app/views/job_invocations/_tab_hosts.html.erb +1 -20
  28. data/app/views/job_invocations/_tab_overview.html.erb +13 -1
  29. data/app/views/job_invocations/show.html.erb +3 -0
  30. data/app/views/job_invocations/show.json.erb +2 -1
  31. data/app/views/template_invocations/_output_line_set.html.erb +1 -1
  32. data/app/views/templates/ssh/package_action.erb +1 -0
  33. data/config/routes.rb +1 -0
  34. data/db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb +34 -0
  35. data/db/migrate/20200820122057_add_proxy_selector_override_to_remote_execution_feature.rb +5 -0
  36. data/db/seeds.d/20-permissions.rb +9 -0
  37. data/lib/foreman_remote_execution/engine.rb +28 -2
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/package.json +6 -6
  40. data/test/functional/api/v2/job_invocations_controller_test.rb +83 -1
  41. data/test/functional/api/v2/registration_controller_test.rb +82 -0
  42. data/test/functional/job_invocations_controller_test.rb +71 -0
  43. data/test/support/remote_execution_helper.rb +5 -0
  44. data/test/unit/actions/run_host_job_test.rb +3 -3
  45. data/test/unit/actions/run_hosts_job_test.rb +3 -2
  46. data/test/unit/job_invocation_composer_test.rb +5 -5
  47. data/test/unit/remote_execution_provider_test.rb +6 -6
  48. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
  49. data/webpack/__mocks__/foremanReact/components/SearchBar.js +2 -0
  50. data/webpack/__mocks__/foremanReact/constants.js +21 -0
  51. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +2 -0
  52. data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/IntervalSelectors.js +1 -0
  53. data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +25 -15
  54. data/webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js +10 -0
  55. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +66 -0
  56. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +6 -0
  57. data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +10 -2
  58. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js +9 -0
  59. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +26 -0
  60. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +17 -2
  61. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +68 -0
  62. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +11 -0
  63. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +35 -19
  64. data/webpack/react_app/components/TargetingHosts/index.js +73 -13
  65. metadata +26 -3
  66. data/webpack/react_app/components/TargetingHosts/TargetingHostsActions.js +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e4ef364cf00a1676f5397c066e7723de19f5ceacd2c3a154dc5c79a79736e80
4
- data.tar.gz: 3fbfc638b7d1ecb65582d321f92d01abd3295316936e0a16973786d3e3848ac4
3
+ metadata.gz: aba1afe9644d85554a140267c452cfd3b11139beaae9f4a8a630b5727480965a
4
+ data.tar.gz: 5d7772b486693036b2f67fbe8ad1a5efe47cd71cdde6c181aeda855e9e86c113
5
5
  SHA512:
6
- metadata.gz: 13b36238ecf725847e3b861e097b26b6b89ca54252621bd29a7b857754ecf2d7ed41f743fbadb6906f56e779784dec94593ec6b30538f265ecc1247de8722044
7
- data.tar.gz: 395fab0f133956214ddd821e9bde1702ee4c0b376ee9d653e778b1a27f3918a68124063a7fa1b568343fb056fe051543b3229a1f4e11815ac7ffc995c1bb0930
6
+ metadata.gz: 32d88414d2e6c0aa3e2dc4395b75a241cd34f912f6083806bdee4c271fce8ace3fbeaa6ec7d7c436eb61fd553b46022576b4a4706af7460122a5cf2ccab70415
7
+ data.tar.gz: 2aafc6f68bd1bb2289ba5fa757a2b8d146d5488d851adc0b2edd88749232ea9b496d30dff85f9ad580c529fbc065002e962f1925802bcdce5d117461481ebfbe
@@ -30,7 +30,7 @@ jobs:
30
30
  strategy:
31
31
  fail-fast: false
32
32
  matrix:
33
- foreman-core-branch: [2.1-stable, develop]
33
+ foreman-core-branch: [develop]
34
34
  ruby-version: [2.5, 2.6]
35
35
  node-version: [12]
36
36
  steps:
@@ -6,7 +6,7 @@ module Api
6
6
 
7
7
  before_action :find_optional_nested_object, :only => %w{output raw_output}
8
8
  before_action :find_host, :only => %w{output raw_output}
9
- before_action :find_resource, :only => %w{show update destroy clone cancel rerun}
9
+ before_action :find_resource, :only => %w{show update destroy clone cancel rerun outputs}
10
10
 
11
11
  wrap_parameters JobInvocation, :include => (JobInvocation.attribute_names + [:ssh])
12
12
 
@@ -137,6 +137,24 @@ module Api
137
137
  end
138
138
  end
139
139
 
140
+ api :GET, '/job_invocations/:id/outputs', N_('Get outputs of hosts in a job')
141
+ param :id, :identifier, :required => true
142
+ param :search_query, :identifier, :required => false
143
+ param :since, String, :required => false
144
+ param :raw, String, :required => false
145
+ def outputs
146
+ hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
147
+ hosts = hosts.search_for(params['search_query']) if params['search_query']
148
+ raw = ActiveRecord::Type::Boolean.new.cast params['raw']
149
+ default_value = raw ? '' : []
150
+ outputs = hosts.map do |host|
151
+ host_output(@job_invocation, host, :default => default_value, :since => params['since'], :raw => raw)
152
+ .merge(host_id: host.id)
153
+ end
154
+
155
+ render :json => { :outputs => outputs }
156
+ end
157
+
140
158
  private
141
159
 
142
160
  def allowed_nested_id
@@ -145,7 +163,7 @@ module Api
145
163
 
146
164
  def action_permission
147
165
  case params[:action]
148
- when 'output', 'raw_output'
166
+ when 'output', 'raw_output', 'outputs'
149
167
  :view
150
168
  when 'cancel'
151
169
  :cancel
@@ -0,0 +1,26 @@
1
+ module ForemanRemoteExecution
2
+ module Concerns
3
+ module Api::V2::RegistrationControllerExtensions
4
+ module ApipieExtensions
5
+ extend Apipie::DSL::Concern
6
+
7
+ update_api(:global, :host) do
8
+ param :remote_execution_interface, String, desc: N_("Identifier of the Host interface for Remote execution")
9
+ end
10
+ end
11
+
12
+ extend ActiveSupport::Concern
13
+
14
+ def host_setup_extension
15
+ remote_execution_interface
16
+ super
17
+ end
18
+
19
+ def remote_execution_interface
20
+ return unless params['remote_execution_interface'].present?
21
+
22
+ @host.set_execution_interface(params['remote_execution_interface'])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module ForemanRemoteExecution
2
+ module Concerns
3
+ module Api::V2::SubnetsControllerExtensions
4
+ module ApiPieExtensions
5
+ extend ::Apipie::DSL::Concern
6
+
7
+ update_api(:create, :update) do
8
+ param :subnet, Hash do
9
+ param :remote_execution_proxy_ids, Array, _('List of proxy IDs to be used for remote execution')
10
+ end
11
+ end
12
+ end
13
+
14
+ extend ActiveSupport::Concern
15
+
16
+ included do
17
+ include ApiPieExtensions
18
+ end
19
+ end
20
+ end
21
+ end
@@ -52,16 +52,18 @@ class JobInvocationsController < ApplicationController
52
52
 
53
53
  def show
54
54
  @job_invocation = resource_base.includes(:template_invocations => :run_host_job_task).find(params[:id])
55
- @auto_refresh = @job_invocation.task.try(:pending?)
56
- @resource_base = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
57
- # There's no need to do the joining if we're not filtering
58
- unless params[:search].nil?
59
- @resource_base = @resource_base.joins(:template_invocations)
60
- .where(:template_invocations => { :job_invocation_id => @job_invocation.id})
61
- end
62
- @hosts = resource_base_search_and_page
63
55
  @job_organization = Taxonomy.find_by(id: @job_invocation.task.input[:current_organization_id])
64
56
  @job_location = Taxonomy.find_by(id: @job_invocation.task.input[:current_location_id])
57
+ @auto_refresh = @job_invocation.task.try(:pending?)
58
+
59
+ respond_to do |format|
60
+ format.json do
61
+ targeting_hosts_resources
62
+ end
63
+
64
+ format.html
65
+ format.js
66
+ end
65
67
  end
66
68
 
67
69
  def index
@@ -153,4 +155,16 @@ class JobInvocationsController < ApplicationController
153
155
  JobInvocationComposer.from_ui_params(with_triggering)
154
156
  end
155
157
  end
158
+
159
+ def targeting_hosts_resources
160
+ @auto_refresh = @job_invocation.task.try(:pending?)
161
+ @resource_base = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
162
+
163
+ unless params[:search].nil?
164
+ @resource_base = @resource_base.joins(:template_invocations)
165
+ .where(:template_invocations => { :job_invocation_id => @job_invocation.id})
166
+ end
167
+ @hosts = resource_base_search_and_page
168
+ @total_hosts = resource_base_with_search.size
169
+ end
156
170
  end
@@ -36,7 +36,7 @@ class JobTemplatesController < ::TemplatesController
36
36
 
37
37
  @template = JobTemplate.import_raw(contents, :update => Foreman::Cast.to_bool(params[:imported_template][:overwrite]))
38
38
  if @template&.save
39
- flash[:notice] = _('Job template imported successfully.')
39
+ flash[:success] = _('Job template imported successfully.')
40
40
  redirect_to job_templates_path(:search => "name = \"#{@template.name}\"")
41
41
  else
42
42
  @template ||= JobTemplate.import_raw(contents, :build_new => true)
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal:true
2
2
 
3
3
  module JobInvocationsHelper
4
- def minicard(icon, number, text)
4
+ def minicard(icon, number, text, tooltip: nil)
5
+ tooltip_options = tooltip ? { :'data-original-title' => tooltip, :rel => 'twipsy' } : {}
5
6
  content_tag(:div, :class => 'card-pf card-pf-accented
6
7
  card-pf-aggregate-status card-pf-aggregate-status-mini') do
7
- content_tag(:h2, :class => 'card-pf-title', :style => 'line-height: 1.1') do
8
+ content_tag(:h2, { :class => 'card-pf-title', :style => 'line-height: 1.1' }.merge(tooltip_options)) do
8
9
  icon_text(icon, '', :kind => 'pficon') +
9
10
  content_tag(:span, number, :class =>'card-pf-aggregate-status-count') +
10
11
  text
@@ -60,7 +60,7 @@ module Actions
60
60
  def secrets(host, job_invocation, provider)
61
61
  job_secrets = { :ssh_password => job_invocation.password,
62
62
  :key_passphrase => job_invocation.key_passphrase,
63
- :sudo_password => job_invocation.sudo_password }
63
+ :effective_user_password => job_invocation.effective_user_password }
64
64
 
65
65
  job_secrets.merge(provider.secrets(host)) { |_key, job_secret, provider_secret| job_secret || provider_secret }
66
66
  end
@@ -47,7 +47,7 @@ module Actions
47
47
  end
48
48
 
49
49
  def finalize
50
- job_invocation.password = job_invocation.key_passphrase = job_invocation.sudo_password = nil
50
+ job_invocation.password = job_invocation.key_passphrase = job_invocation.effective_user_password = nil
51
51
  job_invocation.save!
52
52
 
53
53
  Rails.logger.debug "cleaning cache for keys that begin with 'job_invocation_#{job_invocation.id}'"
@@ -57,15 +57,24 @@ module Actions
57
57
  end
58
58
 
59
59
  def job_invocation
60
- @job_invocation ||= JobInvocation.find(input[:job_invocation_id])
60
+ id = input[:job_invocation_id] || input.fetch(:job_invocation, {})[:id]
61
+ @job_invocation ||= JobInvocation.find(id)
61
62
  end
62
63
 
63
64
  def batch(from, size)
64
65
  hosts.offset(from).limit(size)
65
66
  end
66
67
 
68
+ def initiate
69
+ output[:host_count] = total_count
70
+ super
71
+ end
72
+
67
73
  def total_count
68
- hosts.count
74
+ # For compatibility with already existing tasks
75
+ return output[:total_count] unless output.has_key?(:host_count) || task.pending?
76
+
77
+ output[:host_count] || hosts.count
69
78
  end
70
79
 
71
80
  def hosts
@@ -3,14 +3,33 @@ module ForemanRemoteExecution
3
3
  module Scope
4
4
  class Input < ::Foreman::Renderer::Scope::Template
5
5
  include Foreman::Renderer::Scope::Macros::HostTemplate
6
+ extend ApipieDSL::Class
6
7
 
7
8
  attr_reader :template, :host, :invocation, :input_template_instance, :current_user
8
9
  delegate :input, to: :input_template_instance
9
10
 
11
+ apipie :class, 'Macros related to template rendering' do
12
+ name 'Template Input Render'
13
+ sections only: %w[all jobs]
14
+ end
15
+
16
+ apipie :method, 'Always raises an error with a description provided as an argument' do
17
+ desc 'This method is useful for aborting script execution if some of the conditions are not met'
18
+ required :message, String, desc: 'Description for the error'
19
+ raises error: ::InputTemplateRenderer::RenderError, desc: 'The error is always being raised'
20
+ returns nil, desc: "Doesn't return anything"
21
+ example "<%
22
+ @host.operatingsystem #=> nil
23
+ render_error(N_('Unsupported or no operating system found for this host.')) unless @host.operatingsystem #=> InputTemplateRenderer::RenderError is raised and the execution of the script is aborted
24
+ %>"
25
+ end
10
26
  def render_error(message)
11
27
  raise ::InputTemplateRenderer::RenderError.new(message)
12
28
  end
13
29
 
30
+ apipie :method, 'Check whether the template in preview mode or not' do
31
+ returns one_of: [true, false], desc: 'Returns true if the template in preview mode, false otherwise'
32
+ end
14
33
  def preview?
15
34
  !!@preview
16
35
  end
@@ -23,6 +42,16 @@ module ForemanRemoteExecution
23
42
  Rails.cache.fetch(cache_key, &block)
24
43
  end
25
44
 
45
+ # rubocop:disable Lint/InterpolationCheck
46
+ apipie :method, 'Render template by given name' do
47
+ required :template_name, String, desc: 'name of the template to render'
48
+ optional :input_values, Hash, desc: 'key:value list of input values for the template'
49
+ optional :options, Hash, desc: 'Additional options such as :with_foreign_input_set. Set to true if a foreign input set should be considered when rendering the template'
50
+ raises error: StandardError, desc: 'raises an error if there is no template or template input with such name'
51
+ returns String, desc: 'Rendered template'
52
+ example '<%= render_template("Run Command - Ansible Default", command: "yum -y group install #{input("package")}") %>'
53
+ end
54
+ # rubocop:enable Lint/InterpolationCheck
26
55
  def render_template(template_name, input_values = {}, options = {})
27
56
  options.assert_valid_keys(:with_foreign_input_set)
28
57
  with_foreign_input_set = options.fetch(:with_foreign_input_set, true)
@@ -59,6 +88,12 @@ module ForemanRemoteExecution
59
88
  input_values.merge(overrides).with_indifferent_access
60
89
  end
61
90
 
91
+ apipie :method, 'Returns the value of template input' do
92
+ required :name, String, desc: 'name of the template input'
93
+ raises error: UndefinedInput, desc: 'when there is no input with such name defined for the current template'
94
+ returns Object, desc: 'The value of template input'
95
+ example 'input("Include Facts") #=> "yes"'
96
+ end
62
97
  def input(name)
63
98
  return template_input_values[name.to_s] if template_input_values.key?(name.to_s)
64
99
 
@@ -0,0 +1,13 @@
1
+ module Api
2
+ module V2
3
+ module InterfacesControllerExtensions
4
+ extend Apipie::DSL::Concern
5
+
6
+ update_api(:create, :update) do
7
+ param :interface, Hash do
8
+ param :execution, :bool, :desc => N_('Should this interface be used for remote execution?')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -44,29 +44,33 @@ module ForemanRemoteExecution
44
44
  @execution_status_label ||= get_status(HostStatus::ExecutionStatus).to_label(options)
45
45
  end
46
46
 
47
+ # rubocop:disable Naming/MemoizedInstanceVariableName
47
48
  def host_params_hash
48
- params = super
49
- keys = remote_execution_ssh_keys
50
- source = 'global'
51
- if keys.present?
52
- value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
53
- params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
54
- end
55
- [:remote_execution_ssh_user, :remote_execution_effective_user_method,
56
- :remote_execution_connect_by_ip].each do |key|
57
- value = Setting[key]
58
- params[key.to_s] = {:value => value, :safe_value => value, :source => source} unless params.key?(key.to_s)
59
- end
60
- params
49
+ @cached_rex_host_params_hash ||= extend_host_params_hash(super)
61
50
  end
51
+ # rubocop:enable Naming/MemoizedInstanceVariableName
62
52
 
63
53
  def execution_interface
64
54
  get_interface_by_flag(:execution)
65
55
  end
66
56
 
57
+ def set_execution_interface(identifier)
58
+ if interfaces.find_by(identifier: identifier).nil?
59
+ msg = (N_("Interface with the '%s' identifier was specified as a remote execution interface, however the interface was not found on the host. If the interface exists, it needs to be created in Foreman during the registration.") % identifier)
60
+ raise ActiveRecord::RecordNotFound, msg
61
+ end
62
+
63
+ # Only one interface at time can be used for REX, all other must be set to false
64
+ interfaces.each { |int| int.execution = (int.identifier == identifier) }
65
+ interfaces.each(&:save!)
66
+ end
67
+
67
68
  def remote_execution_proxies(provider, authorized = true)
68
69
  proxies = {}
69
- proxies[:subnet] = execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface&.subnet
70
+ proxies[:subnet] = []
71
+ proxies[:subnet] += execution_interface.subnet6.remote_execution_proxies.with_features(provider) if execution_interface&.subnet6
72
+ proxies[:subnet] += execution_interface.subnet.remote_execution_proxies.with_features(provider) if execution_interface&.subnet
73
+ proxies[:subnet].uniq!
70
74
  proxies[:fallback] = smart_proxies.with_features(provider) if Setting[:remote_execution_fallback_proxy]
71
75
 
72
76
  if Setting[:remote_execution_global_proxy]
@@ -102,8 +106,28 @@ module ForemanRemoteExecution
102
106
  super(*args)
103
107
  end
104
108
 
109
+ def clear_host_parameters_cache!
110
+ super
111
+ @cached_rex_host_params_hash = nil
112
+ end
113
+
105
114
  private
106
115
 
116
+ def extend_host_params_hash(params)
117
+ keys = remote_execution_ssh_keys
118
+ source = 'global'
119
+ if keys.present?
120
+ value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
121
+ params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
122
+ end
123
+ [:remote_execution_ssh_user, :remote_execution_effective_user_method,
124
+ :remote_execution_connect_by_ip].each do |key|
125
+ value = Setting[key]
126
+ params[key.to_s] = {:value => value, :safe_value => value, :source => source} unless params.key?(key.to_s)
127
+ end
128
+ params
129
+ end
130
+
107
131
  def build_required_interfaces(attrs = {})
108
132
  super(attrs)
109
133
  self.primary_interface.execution = true if self.execution_interface.nil?
@@ -22,6 +22,7 @@ class JobInvocation < ApplicationRecord
22
22
  validates :job_category, :presence => true
23
23
  validates_associated :targeting, :all_template_invocations
24
24
 
25
+ scoped_search :on => :id, :complete_value => true
25
26
  scoped_search :on => :job_category, :complete_value => true
26
27
  scoped_search :on => :description, :complete_value => true
27
28
 
@@ -41,7 +42,10 @@ class JobInvocation < ApplicationRecord
41
42
  has_many :template_invocation_tasks, :through => :template_invocations,
42
43
  :class_name => 'ForemanTasks::Task',
43
44
  :source => 'run_host_job_task'
44
-
45
+ has_one :user, through: :task
46
+ scoped_search relation: :user, on: :login, rename: 'user', complete_value: true,
47
+ value_translation: ->(value) { value == 'current_user' ? User.current.login : value },
48
+ special_values: [:current_user], aliases: ['owner'], :only_explicit => true
45
49
  scoped_search :relation => :task, :on => :started_at, :rename => 'started_at', :complete_value => true
46
50
  scoped_search :relation => :task, :on => :start_at, :rename => 'start_at', :complete_value => true
47
51
  scoped_search :relation => :task, :on => :ended_at, :rename => 'ended_at', :complete_value => true
@@ -70,7 +74,7 @@ class JobInvocation < ApplicationRecord
70
74
 
71
75
  delegate :start_at, :to => :task, :allow_nil => true
72
76
 
73
- encrypts :password, :key_passphrase, :sudo_password
77
+ encrypts :password, :key_passphrase, :effective_user_password
74
78
 
75
79
  def self.search_by_status(key, operator, value)
76
80
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
@@ -139,7 +143,7 @@ class JobInvocation < ApplicationRecord
139
143
  invocation.pattern_template_invocations = self.pattern_template_invocations.map(&:deep_clone)
140
144
  invocation.password = self.password
141
145
  invocation.key_passphrase = self.key_passphrase
142
- invocation.sudo_password = self.sudo_password
146
+ invocation.effective_user_password = self.effective_user_password
143
147
  end
144
148
  end
145
149
 
@@ -170,7 +174,7 @@ class JobInvocation < ApplicationRecord
170
174
 
171
175
  def total_hosts_count
172
176
  if targeting.resolved?
173
- targeting.hosts.count
177
+ task&.main_action&.total_count || targeting.hosts.count
174
178
  else
175
179
  _('N/A')
176
180
  end
@@ -240,6 +244,10 @@ class JobInvocation < ApplicationRecord
240
244
  !task.pending?
241
245
  end
242
246
 
247
+ def missing_hosts_count
248
+ targeting.resolved? ? total_hosts_count - targeting.hosts.count : 0
249
+ end
250
+
243
251
  private
244
252
 
245
253
  def failed_template_invocations
@@ -15,7 +15,7 @@ class JobInvocationComposer
15
15
  :description_format => job_invocation_base[:description_format],
16
16
  :password => blank_to_nil(job_invocation_base[:password]),
17
17
  :key_passphrase => blank_to_nil(job_invocation_base[:key_passphrase]),
18
- :sudo_password => blank_to_nil(job_invocation_base[:sudo_password]),
18
+ :effective_user_password => blank_to_nil(job_invocation_base[:effective_user_password]),
19
19
  :concurrency_control => concurrency_control_params,
20
20
  :execution_timeout_interval => execution_timeout_interval,
21
21
  :template_invocations => template_invocations_params }.with_indifferent_access
@@ -348,7 +348,7 @@ class JobInvocationComposer
348
348
  job_invocation.execution_timeout_interval = params[:execution_timeout_interval]
349
349
  job_invocation.password = params[:password]
350
350
  job_invocation.key_passphrase = params[:key_passphrase]
351
- job_invocation.sudo_password = params[:sudo_password]
351
+ job_invocation.effective_user_password = params[:effective_user_password]
352
352
 
353
353
  if @reruns && job_invocation.targeting.static?
354
354
  job_invocation.targeting.host_ids = JobInvocation.find(@reruns).targeting.host_ids
@@ -482,7 +482,7 @@ class JobInvocationComposer
482
482
 
483
483
  def input_value_for(input)
484
484
  invocations = pattern_template_invocations
485
- default = TemplateInvocationInputValue.new
485
+ default = TemplateInvocationInputValue.new(:template_input_id => input.id)
486
486
  invocations.map(&:input_values).flatten.detect { |iv| iv.template_input_id == input.id } || default
487
487
  end
488
488