foreman_remote_execution 4.4.0 → 4.5.3

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +13 -24
  3. data/app/controllers/job_invocations_controller.rb +1 -1
  4. data/app/controllers/job_templates_controller.rb +4 -4
  5. data/app/controllers/ui_job_wizard_controller.rb +19 -0
  6. data/app/helpers/job_invocations_helper.rb +2 -2
  7. data/app/helpers/remote_execution_helper.rb +13 -9
  8. data/app/lib/actions/remote_execution/run_host_job.rb +36 -6
  9. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  10. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  11. data/app/models/host_proxy_invocation.rb +4 -0
  12. data/app/models/host_status/execution_status.rb +5 -5
  13. data/app/models/job_invocation.rb +31 -12
  14. data/app/models/job_invocation_composer.rb +61 -19
  15. data/app/models/remote_execution_provider.rb +1 -1
  16. data/app/models/setting/remote_execution.rb +2 -2
  17. data/app/models/ssh_execution_provider.rb +4 -4
  18. data/app/models/targeting.rb +5 -1
  19. data/app/overrides/execution_interface.rb +8 -8
  20. data/app/overrides/subnet_proxies.rb +6 -6
  21. data/app/views/job_invocations/index.html.erb +1 -1
  22. data/app/views/templates/ssh/module_action.erb +1 -0
  23. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  24. data/config/routes.rb +1 -0
  25. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  26. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  27. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  28. data/extra/cockpit/foreman-cockpit-session +6 -6
  29. data/lib/foreman_remote_execution/engine.rb +11 -8
  30. data/lib/foreman_remote_execution/version.rb +1 -1
  31. data/package.json +2 -1
  32. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  33. data/test/unit/job_invocation_composer_test.rb +59 -2
  34. data/test/unit/job_invocation_test.rb +1 -1
  35. data/webpack/JobWizard/JobWizard.js +80 -19
  36. data/webpack/JobWizard/JobWizard.scss +42 -1
  37. data/webpack/JobWizard/JobWizardConstants.js +11 -0
  38. data/webpack/JobWizard/JobWizardSelectors.js +27 -1
  39. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  40. data/webpack/JobWizard/__tests__/fixtures.js +128 -0
  41. data/webpack/JobWizard/__tests__/integration.test.js +84 -0
  42. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +110 -0
  43. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  44. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +195 -0
  45. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +144 -0
  46. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  47. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +34 -2
  48. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -44
  49. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +9 -1
  50. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  51. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  52. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  53. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  54. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  55. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  56. data/webpack/JobWizard/steps/form/FormHelpers.js +20 -0
  57. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  58. data/webpack/JobWizard/steps/form/GroupedSelectField.js +3 -0
  59. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  60. data/webpack/JobWizard/steps/form/SelectField.js +24 -3
  61. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  62. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  63. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  64. data/webpack/global_index.js +5 -3
  65. data/webpack/index.js +3 -0
  66. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -5
  67. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  68. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  69. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  70. data/webpack/react_app/extend/{fills.js → fillRecentJobsCard.js} +7 -6
  71. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  72. data/webpack/react_app/extend/reducers.js +2 -1
  73. metadata +24 -14
  74. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  75. data/test/models/orchestration/ssh_test.rb +0 -56
  76. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -20
  77. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -83
  78. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -64
  79. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  80. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  81. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -36
  82. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -22
  83. data/webpack/fills_index.js +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28b7b4b36385dfde5221cc7d9b9d07d95f4515dafb657507015b9436086474ca
4
- data.tar.gz: 27d3907329bcda250a9518903f069de480fda0609a53134369c2a348a31fb29a
3
+ metadata.gz: 812237a4d9fd4c4000c8522be1ecdaf539f8ca15e9b6e63a599da92419ade6f4
4
+ data.tar.gz: 8a0c5f90892a268816c0b16184faef14b55a36f60fdd0750303291d0688ba83f
5
5
  SHA512:
6
- metadata.gz: 9bfc761d4d8343a7fce864c3155cea9d4a3c67ff42b60359901e8be0f890ddfc63f536e3288133877a448cbee367063a990619a1b85007ab8f0433f4acd917b6
7
- data.tar.gz: 05b1af5c16a8c0d542b9876f86810e8ca2c44dbe4165ff10eccf08eb1172edacc94e68025e486324c52648be0b1cfaf2e85ceb552755428a5917163c28febfad
6
+ metadata.gz: a572fa58d1c7b1776c5493afdb9e2940f97e03a996e5e56c5ed633497b825a8e77b2013a7570c8990403f3f2e0f197036aee556c7177cae3fb6feac585f5c95b
7
+ data.tar.gz: 57d64d09cbd7b14984564201e53df9a76bd6e95fc7008283e506924813d7ee18996ece236d2d6258da4f61a8a56816491b9b690a01f36e0a94de364db4c5ba64
@@ -80,18 +80,15 @@ module Api
80
80
  api :POST, '/job_invocations/', N_('Create a job invocation')
81
81
  param_group :job_invocation, :as => :create
82
82
  def create
83
- if job_invocation_params[:feature].present?
84
- composer = composer_for_feature
85
- else
86
- validate_template
87
- composer = JobInvocationComposer.from_api_params(
88
- job_invocation_params
89
- )
90
- end
83
+ composer = JobInvocationComposer.from_api_params(
84
+ job_invocation_params
85
+ )
91
86
  composer.trigger!
92
87
  @job_invocation = composer.job_invocation
93
88
  @hosts = @job_invocation.targeting.hosts
94
89
  process_response @job_invocation
90
+ rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e
91
+ not_found(error: { message: e.message })
95
92
  end
96
93
 
97
94
  api :GET, '/job_invocations/:id/hosts/:host_id', N_('Get output for a host')
@@ -128,7 +125,7 @@ module Api
128
125
  render :json => { :cancelled => result, :id => @job_invocation.id }
129
126
  else
130
127
  render :json => { :message => _('The job could not be cancelled.') },
131
- :status => :unprocessable_entity
128
+ :status => :unprocessable_entity
132
129
  end
133
130
  end
134
131
 
@@ -143,7 +140,7 @@ module Api
143
140
  process_response @job_invocation
144
141
  else
145
142
  render :json => { :error => _('Could not rerun job %{id} because its template could not be found') % { :id => composer.reruns } },
146
- :status => :not_found
143
+ :status => :not_found
147
144
  end
148
145
  end
149
146
 
@@ -190,19 +187,19 @@ module Api
190
187
  not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
191
188
  end
192
189
 
193
- def validate_template
194
- JobTemplate.authorized(:view_job_templates).find(job_invocation_params['job_template_id'])
195
- rescue ActiveRecord::RecordNotFound
196
- not_found({ :error => { :message => (_("Template with id '%{id}' was not found") % { :id => job_invocation_params['job_template_id'] }) } })
197
- end
198
-
199
190
  def job_invocation_params
200
191
  return @job_invocation_params if @job_invocation_params.present?
201
192
 
202
193
  job_invocation_params = params.fetch(:job_invocation, {}).dup
194
+
195
+ if job_invocation_params[:feature].present? && job_invocation_params[:job_template_id].present?
196
+ raise _("Only one of feature or job_template_id can be specified")
197
+ end
198
+
203
199
  if job_invocation_params.key?(:ssh)
204
200
  job_invocation_params.merge!(job_invocation_params.delete(:ssh).permit(:effective_user))
205
201
  end
202
+
206
203
  job_invocation_params[:inputs] ||= {}
207
204
  job_invocation_params[:inputs].permit!
208
205
  permit_provider_inputs job_invocation_params
@@ -214,14 +211,6 @@ module Api
214
211
  providers.each { |provider| invocation_params[provider.provider_input_namespace]&.permit! }
215
212
  end
216
213
 
217
- def composer_for_feature
218
- JobInvocationComposer.for_feature(
219
- job_invocation_params[:feature],
220
- job_invocation_params[:host_ids],
221
- job_invocation_params[:inputs].to_hash
222
- )
223
- end
224
-
225
214
  def output_lines_since(task, time)
226
215
  since = time.to_f if time.present?
227
216
  line_sets = task.main_action.live_output
@@ -67,7 +67,7 @@ class JobInvocationsController < ApplicationController
67
67
  end
68
68
 
69
69
  def index
70
- @job_invocations = resource_base_search_and_page.with_task.order('job_invocations.id DESC')
70
+ @job_invocations = resource_base_search_and_page.preload(:task, :targeting).order('job_invocations.id DESC')
71
71
  end
72
72
 
73
73
  # refreshes the form
@@ -24,10 +24,10 @@ class JobTemplatesController < ::TemplatesController
24
24
  render :plain => output
25
25
  else
26
26
  render status: :not_acceptable,
27
- plain: _(
28
- 'Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' %
29
- {:error => renderer.error_message}
30
- )
27
+ plain: _(
28
+ 'Problem with previewing the template: %{error}. Note that you must save template input changes before you try to preview it.' %
29
+ {:error => renderer.error_message}
30
+ )
31
31
  end
32
32
  end
33
33
 
@@ -8,6 +8,25 @@ class UiJobWizardController < ::Api::V2::BaseController
8
8
  render :json => {:job_categories =>job_categories}
9
9
  end
10
10
 
11
+ def template
12
+ job_template = JobTemplate.authorized.find(params[:id])
13
+ advanced_template_inputs, template_inputs = map_template_inputs(job_template.template_inputs_with_foreign).partition { |x| x["advanced"] }
14
+ render :json => {
15
+ :job_template => job_template,
16
+ :effective_user => job_template.effective_user,
17
+ :template_inputs => template_inputs,
18
+ :advanced_template_inputs => advanced_template_inputs,
19
+ }
20
+ end
21
+
22
+ def resource_name(nested_resource = nil)
23
+ nested_resource || 'job_template'
24
+ end
25
+
26
+ def map_template_inputs(template_inputs_with_foreign)
27
+ template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type => input.resource_type&.tableize }) }
28
+ end
29
+
11
30
  def resource_class
12
31
  JobTemplate
13
32
  end
@@ -34,8 +34,8 @@ module JobInvocationsHelper
34
34
  hosts.map do |host|
35
35
  collapsed_preview(host) +
36
36
  render(:partial => 'job_invocations/user_input',
37
- :locals => { :template_invocation => template_invocation,
38
- :target => host })
37
+ :locals => { :template_invocation => template_invocation,
38
+ :target => host })
39
39
  end.reduce(:+)
40
40
  end
41
41
 
@@ -7,6 +7,10 @@ module RemoteExecutionHelper
7
7
  @job_hosts_authorizer ||= Authorizer.new(User.current, :collection => @hosts)
8
8
  end
9
9
 
10
+ def host_tasks_authorizer
11
+ @host_tasks_authorizer ||= Authorizer.new(User.current, :collection => @job_invocation.sub_tasks)
12
+ end
13
+
10
14
  def host_counter(label, count)
11
15
  content_tag(:div, :class => 'host_counter') do
12
16
  content_tag(:div, label, :class => 'header') + content_tag(:div, count.to_s, :class => 'count')
@@ -27,19 +31,19 @@ module RemoteExecutionHelper
27
31
 
28
32
  if authorized_for(hash_for_host_path(host).merge(auth_object: host, permission: :view_hosts, authorizer: job_hosts_authorizer))
29
33
  links << { title: _('Host detail'),
30
- action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
34
+ action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
31
35
  end
32
36
 
33
37
  if authorized_for(hash_for_rerun_job_invocation_path(id: job_invocation, host_ids: [ host.id ], authorizer: job_hosts_authorizer))
34
38
  links << { title: (_('Rerun on %s') % host.name),
35
- action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
36
- 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
39
+ action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
40
+ 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
37
41
  end
38
42
 
39
- if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
43
+ if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks, authorizer: host_tasks_authorizer))
40
44
  links << { title: _('Host task'),
41
- action: { href: foreman_tasks_task_path(host_task),
42
- 'data-method': 'get', id: "#{host.name}-actions-task" } }
45
+ action: { href: foreman_tasks_task_path(host_task),
46
+ 'data-method': 'get', id: "#{host.name}-actions-task" } }
43
47
  end
44
48
 
45
49
  links
@@ -159,11 +163,11 @@ module RemoteExecutionHelper
159
163
  content_tag :pre, preview
160
164
  elsif target.nil?
161
165
  alert :text => _('Could not render the preview because no host matches the search query.'),
162
- :class => 'alert alert-block alert-warning base',
163
- :close => false
166
+ :class => 'alert alert-block alert-warning base',
167
+ :close => false
164
168
  else
165
169
  alert :class => 'alert-block alert-danger base in fade has-error',
166
- :text => renderer.error_message.html_safe # rubocop:disable Rails/OutputSafety
170
+ :text => renderer.error_message.html_safe # rubocop:disable Rails/OutputSafety
167
171
  end
168
172
  end
169
173
 
@@ -5,6 +5,8 @@ module Actions
5
5
  include ::Actions::Helpers::WithDelegatedAction
6
6
  include ::Actions::ObservableAction
7
7
 
8
+ execution_plan_hooks.use :emit_feature_event, :on => :success
9
+
8
10
  middleware.do_not_use Dynflow::Middleware::Common::Transaction
9
11
  middleware.use Actions::Middleware::HideSecrets
10
12
 
@@ -17,7 +19,12 @@ module Actions
17
19
  end
18
20
 
19
21
  def plan(job_invocation, host, template_invocation, proxy_selector = ::RemoteExecutionProxySelector.new, options = {})
20
- action_subject(host, :job_category => job_invocation.job_category, :description => job_invocation.description, :job_invocation_id => job_invocation.id)
22
+ features = template_invocation.template.remote_execution_features.pluck(:label).uniq
23
+ action_subject(host,
24
+ :job_category => job_invocation.job_category,
25
+ :description => job_invocation.description,
26
+ :job_invocation_id => job_invocation.id,
27
+ :job_features => features)
21
28
 
22
29
  template_invocation.host_id = host.id
23
30
  template_invocation.run_host_job_task_id = task.id
@@ -40,12 +47,16 @@ module Actions
40
47
  script = renderer.render
41
48
  raise _('Failed rendering template: %s') % renderer.error_message unless script
42
49
 
50
+ first_execution = host.executed_through_proxies.where(:id => proxy.id).none?
51
+ host.executed_through_proxies << proxy if first_execution
52
+
43
53
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
44
54
  :script => script,
45
55
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
46
56
  :secrets => secrets(host, job_invocation, provider),
47
57
  :use_batch_triggering => true,
48
- :use_concurrency_control => options[:use_concurrency_control]}
58
+ :use_concurrency_control => options[:use_concurrency_control],
59
+ :first_execution => first_execution }
49
60
  action_options = provider.proxy_command_options(template_invocation, host)
50
61
  .merge(additional_options)
51
62
 
@@ -58,6 +69,22 @@ module Actions
58
69
  check_exit_status
59
70
  end
60
71
 
72
+ def self.feature_job_event_name(label, suffix = :success)
73
+ ::Foreman::Observable.event_name_for("#{::Actions::RemoteExecution::RunHostJob.event_name_base}_#{label}_#{::Actions::RemoteExecution::RunHostJob.event_name_suffix(suffix)}")
74
+ end
75
+
76
+ def emit_feature_event(execution_plan, hook = :success)
77
+ return unless root_action?
78
+
79
+ payload = event_payload(execution_plan)
80
+ if input["job_features"]&.any?
81
+ input['job_features'].each do |feature|
82
+ name = "#{self.class.event_name_base}_#{feature}_#{self.class.event_name_suffix(hook)}"
83
+ trigger_hook name, payload: payload
84
+ end
85
+ end
86
+ end
87
+
61
88
  def secrets(host, job_invocation, provider)
62
89
  job_secrets = { :ssh_password => job_invocation.password,
63
90
  :key_passphrase => job_invocation.key_passphrase,
@@ -78,7 +105,7 @@ module Actions
78
105
 
79
106
  def humanized_input
80
107
  N_('%{description} on %{host}') % { :host => input[:host].try(:[], :name),
81
- :description => input[:description].try(:capitalize) || input[:job_category] }
108
+ :description => input[:description].try(:capitalize) || input[:job_category] }
82
109
  end
83
110
 
84
111
  def humanized_name
@@ -186,10 +213,13 @@ module Actions
186
213
  'All %{count} applicable proxies are down. Tried %{proxy_names}',
187
214
  offline_proxies.count) % settings
188
215
  elsif proxy == :not_defined
189
- settings = { :global_proxy => 'remote_execution_global_proxy',
190
- :fallback_proxy => 'remote_execution_fallback_proxy' }
216
+ settings = {
217
+ global_proxy: 'remote_execution_global_proxy',
218
+ fallback_proxy: 'remote_execution_fallback_proxy',
219
+ provider: provider,
220
+ }
191
221
 
192
- raise _('Could not use any proxy. Consider configuring %{global_proxy}, ' +
222
+ raise _('Could not use any proxy for the %{provider} job. Consider configuring %{global_proxy}, ' +
193
223
  '%{fallback_proxy} in settings') % settings
194
224
  end
195
225
  proxy
@@ -6,17 +6,19 @@ module ForemanRemoteExecution
6
6
  has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
7
7
  has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id', :dependent => :destroy
8
8
  has_many :run_host_job_tasks, :through => :template_invocations
9
+ has_many :host_proxy_invocations, :foreign_key => 'host_id', :dependent => :destroy
10
+ has_many :executed_through_proxies, :through => :host_proxy_invocations, :source => 'smart_proxy'
9
11
 
10
12
  scoped_search :relation => :run_host_job_tasks, :on => :result, :rename => 'job_invocation.result',
11
- :ext_method => :search_by_job_invocation,
12
- :only_explicit => true,
13
- :complete_value => TemplateInvocation::TaskResultMap::REVERSE_MAP
13
+ :ext_method => :search_by_job_invocation,
14
+ :only_explicit => true,
15
+ :complete_value => TemplateInvocation::TaskResultMap::REVERSE_MAP
14
16
 
15
17
  scoped_search :relation => :template_invocations, :on => :job_invocation_id,
16
- :rename => 'job_invocation.id', :only_explicit => true, :ext_method => :search_by_job_invocation
18
+ :rename => 'job_invocation.id', :only_explicit => true, :ext_method => :search_by_job_invocation
17
19
 
18
20
  scoped_search :relation => :execution_status_object, :on => :status, :rename => :execution_status,
19
- :complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
21
+ :complete_value => { :ok => HostStatus::ExecutionStatus::OK, :error => HostStatus::ExecutionStatus::ERROR }
20
22
 
21
23
  def search_by_job_invocation(key, operator, value)
22
24
  if key == 'job_invocation.result'
@@ -1,5 +1,11 @@
1
1
  module ForemanRemoteExecution
2
2
  module SmartProxyExtensions
3
+ def self.prepended(base)
4
+ base.instance_eval do
5
+ has_many :host_proxy_invocations, :dependent => :destroy
6
+ end
7
+ end
8
+
3
9
  def pubkey
4
10
  self[:pubkey] || update_pubkey
5
11
  end
@@ -0,0 +1,4 @@
1
+ class HostProxyInvocation < ApplicationRecord
2
+ belongs_to :host, :class_name => 'Host::Managed', :inverse_of => :host_proxy_invocations
3
+ belongs_to :smart_proxy
4
+ end
@@ -64,15 +64,15 @@ class HostStatus::ExecutionStatus < HostStatus::Status
64
64
 
65
65
  case status
66
66
  when OK
67
- [ "state = 'stopped' AND result = 'success'" ]
67
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'success'" ]
68
68
  when CANCELLED
69
- [ "state = 'stopped' AND result = 'cancelled'" ]
69
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'cancelled'" ]
70
70
  when ERROR
71
- [ "state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
71
+ [ "foreman_tasks_tasks.state = 'stopped' AND (foreman_tasks_tasks.result = 'error' OR foreman_tasks_tasks.result = 'warning')" ]
72
72
  when QUEUED
73
- [ "state = 'scheduled' OR state IS NULL" ]
73
+ [ "foreman_tasks_tasks.state = 'scheduled' OR foreman_tasks_tasks.state IS NULL" ]
74
74
  when RUNNING
75
- [ "state <> 'stopped'" ]
75
+ [ "foreman_tasks_tasks.state <> 'stopped'" ]
76
76
  else
77
77
  [ '1 = 0' ]
78
78
  end
@@ -15,8 +15,9 @@ class JobInvocation < ApplicationRecord
15
15
 
16
16
  belongs_to :targeting, :dependent => :destroy
17
17
  has_many :all_template_invocations, :inverse_of => :job_invocation, :dependent => :destroy, :class_name => 'TemplateInvocation'
18
- has_many :template_invocations, -> { where('host_id IS NOT NULL') }, :inverse_of => :job_invocation
19
- has_many :pattern_template_invocations, -> { where('host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
18
+ has_many :template_invocations, -> { where('template_invocations.host_id IS NOT NULL') }, :inverse_of => :job_invocation
19
+ has_many :pattern_template_invocations, -> { where('template_invocations.host_id IS NULL') }, :inverse_of => :job_invocation, :class_name => 'TemplateInvocation'
20
+ has_many :pattern_templates, :through => :pattern_template_invocations, :source => :template
20
21
 
21
22
  validates :targeting, :presence => true
22
23
  validates :job_category, :presence => true
@@ -44,26 +45,31 @@ class JobInvocation < ApplicationRecord
44
45
  :source => 'run_host_job_task'
45
46
  has_one :user, through: :task
46
47
  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
48
+ value_translation: ->(value) { value == 'current_user' ? User.current.login : value },
49
+ special_values: [:current_user], aliases: ['owner'], :only_explicit => true
49
50
  scoped_search :relation => :task, :on => :started_at, :rename => 'started_at', :complete_value => true
50
51
  scoped_search :relation => :task, :on => :start_at, :rename => 'start_at', :complete_value => true
51
52
  scoped_search :relation => :task, :on => :ended_at, :rename => 'ended_at', :complete_value => true
52
53
  scoped_search :relation => :task, :on => :state, :rename => 'status', :ext_method => :search_by_status,
53
- :only_explicit => true, :complete_value => Hash[HostStatus::ExecutionStatus::STATUS_NAMES.values.map { |v| [v, v] }]
54
+ :only_explicit => true, :complete_value => Hash[HostStatus::ExecutionStatus::STATUS_NAMES.values.map { |v| [v, v] }]
54
55
 
55
56
  belongs_to :triggering, :class_name => 'ForemanTasks::Triggering'
56
57
  has_one :recurring_logic, :through => :triggering, :class_name => 'ForemanTasks::RecurringLogic'
57
58
 
58
59
  belongs_to :remote_execution_feature
59
60
 
60
- scope :with_task, -> { references(:task) }
61
+ has_many :targeted_hosts, :through => :targeting, :source => :hosts
62
+ scoped_search :on => 'targeted_host_id', :rename => 'targeted_host_id', :operators => ['= '],
63
+ :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host
64
+
65
+ scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
66
+ :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
61
67
 
62
68
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
63
69
 
64
70
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
65
- :ext_method => :search_by_recurring_logic, :only_explicit => true,
66
- :complete_value => { :true => true, :false => false }
71
+ :ext_method => :search_by_recurring_logic, :only_explicit => true,
72
+ :complete_value => { :true => true, :false => false }
67
73
 
68
74
  default_scope -> { order('job_invocations.id DESC') }
69
75
 
@@ -80,10 +86,18 @@ class JobInvocation < ApplicationRecord
80
86
  allow :sub_task_for_host, :template_invocations_hosts
81
87
  end
82
88
 
89
+ def self.search_by_targeted_host(key, operator, value)
90
+ { :conditions => sanitize_sql_for_conditions(["hosts.id = ?", value]), :joins => :targeted_hosts }
91
+ end
92
+
93
+ def self.search_by_pattern_template(key, operator, value)
94
+ { :conditions => sanitize_sql_for_conditions(["templates.name = ?", value]), :joins => :pattern_templates }
95
+ end
96
+
83
97
  def self.search_by_status(key, operator, value)
84
98
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
85
99
  conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
86
- { :conditions => sanitize_sql_for_conditions(conditions), :include => :task }
100
+ { :conditions => sanitize_sql_for_conditions(conditions), :joins => :task }
87
101
  end
88
102
 
89
103
  def self.search_by_recurring_logic(key, operator, value)
@@ -177,11 +191,16 @@ class JobInvocation < ApplicationRecord
177
191
  end
178
192
 
179
193
  def total_hosts_count
194
+ count = _('N/A')
195
+
180
196
  if targeting.resolved?
181
- task&.main_action&.total_count || targeting.hosts.count
182
- else
183
- _('N/A')
197
+ count = if task&.main_action.respond_to?(:total_count)
198
+ task.main_action.total_count
199
+ else
200
+ targeting.hosts.count
201
+ end
184
202
  end
203
+ count
185
204
  end
186
205
 
187
206
  def pattern_template_invocation_for_host(host)