foreman_remote_execution 3.3.5 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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 +2 -2
  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 +2 -2
  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 +84 -2
  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 +16 -1
  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 +30 -7
  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: 6add16b63c650250f993954c92620863d9904055d4b175fa76b04b97dd70ee92
4
- data.tar.gz: 7fca35b0b17e7f2ad0f5769fae4f56b0d5e6f0c85b9ebcc8abab29858bf585a5
3
+ metadata.gz: db51f6eb1c4c3513b55e3420cd28ee22e956980ef8b82845e7e65cf5389afe32
4
+ data.tar.gz: ce148bbcf61c208f646a68153ba36331d1a994476690d4a5d37551fda47fc25b
5
5
  SHA512:
6
- metadata.gz: 359e21b88b85d45b1c4faef9a7e841b0c34e008d3e818c111eba4079167683ff6c59e1354f2f2aafc18e7be43c93c00c368eb9d331636d6c3103d6c59f637306
7
- data.tar.gz: 17aa0693af9ec50fca1a97e4e685c914a665edfe78cf0d9a87b17a9c683490ad7b151748c36adb9b9bcb50389afaca533065d4590d23f2b7550eddad8017538c
6
+ metadata.gz: 28dda5271e680ab81d7d38aea31e250b01c930a6793417deb4d2204975298e48d14c17880699d4354662ad35381566b60e6fd04f8b2ebdb5d89599cab9bf5e74
7
+ data.tar.gz: 298f020eacc63c41e0ef3d198ba45723f629ce991931126c3cf3a446677439dccf4a1a24d1b1c14b110423964ab5f04aac44a3fb8acab10b6e5d2d4f994a9f2f
@@ -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