foreman_remote_execution 4.5.0 → 4.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/job_invocations_controller.rb +1 -1
  3. data/app/controllers/ui_job_wizard_controller.rb +7 -0
  4. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
  5. data/app/helpers/remote_execution_helper.rb +9 -3
  6. data/app/lib/actions/remote_execution/run_host_job.rb +5 -1
  7. data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
  8. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -0
  9. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  10. data/app/models/host_proxy_invocation.rb +4 -0
  11. data/app/models/host_status/execution_status.rb +3 -3
  12. data/app/models/job_invocation.rb +9 -6
  13. data/app/models/job_invocation_composer.rb +4 -4
  14. data/app/models/remote_execution_feature.rb +5 -1
  15. data/app/models/remote_execution_provider.rb +1 -1
  16. data/app/models/setting/remote_execution.rb +2 -2
  17. data/app/models/targeting.rb +5 -1
  18. data/app/views/job_invocations/index.html.erb +1 -1
  19. data/app/views/templates/ssh/module_action.erb +1 -0
  20. data/app/views/templates/ssh/power_action.erb +2 -0
  21. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  22. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  23. data/lib/foreman_remote_execution/engine.rb +0 -2
  24. data/lib/foreman_remote_execution/version.rb +1 -1
  25. data/test/unit/job_invocation_composer_test.rb +14 -1
  26. data/test/unit/job_invocation_test.rb +1 -1
  27. data/webpack/JobWizard/JobWizard.js +28 -8
  28. data/webpack/JobWizard/JobWizard.scss +39 -0
  29. data/webpack/JobWizard/JobWizardConstants.js +10 -0
  30. data/webpack/JobWizard/JobWizardSelectors.js +9 -0
  31. data/webpack/JobWizard/__tests__/fixtures.js +104 -2
  32. data/webpack/JobWizard/__tests__/integration.test.js +13 -85
  33. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
  34. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  35. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
  36. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
  37. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  38. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
  39. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  40. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  41. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  42. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  43. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  44. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  45. data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
  46. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  47. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  48. data/webpack/JobWizard/steps/form/SelectField.js +14 -2
  49. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  50. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  51. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  52. metadata +15 -13
  53. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  54. data/test/models/orchestration/ssh_test.rb +0 -56
  55. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  56. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  57. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
  58. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  59. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  60. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  61. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  62. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113fb04d80f92308f55da87e148b2c30de510b5a97dda5c400bdac598d3a407f
4
- data.tar.gz: 42a44014bbb85af6ccef059d4d716a9e840865be30735cea61fb65cd94a63d2f
3
+ metadata.gz: 8db3f88983ab094bd33ad6cd7630fdc57924e73ba196b1393c1369512cbd3307
4
+ data.tar.gz: 8bdaa93cdeabbb00ff6af832842c2fc1bb6437b0f3467db511e86cf22a5742bd
5
5
  SHA512:
6
- metadata.gz: 6321f8795de48743746748a71b3a64d4c75de23f55bfe0e79bfd660a54ecd2b5522f10554fa96c16d11350a6800457b47ae0a82fec7f905142f9d062103b3ce6
7
- data.tar.gz: 41962649438f42c27845b6e7b6694cff343c4f44e8b7771534a25127458ed2b9a15b404e319e00958f36f81a0d139733820873dd5a2771cb5efeaaebbbfa7223
6
+ metadata.gz: 6b03559c1efa565ec741874f4213ec86dcd4115b4dcf896c9846100033429baa5bbe1df427f8f2e4a4202d62e9dbc8768358bc3034d86362bb1d40236061c2f4
7
+ data.tar.gz: 7091c88b57902cadf6c59959563d25332a89731f9a2da44a267bcd5207611a3256c31b0971f0cd9f2dca05186a07e5f86fb1361ac47d9d69bd11d61df6628e69
@@ -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
@@ -10,9 +10,12 @@ class UiJobWizardController < ::Api::V2::BaseController
10
10
 
11
11
  def template
12
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"] }
13
14
  render :json => {
14
15
  :job_template => job_template,
15
16
  :effective_user => job_template.effective_user,
17
+ :template_inputs => template_inputs,
18
+ :advanced_template_inputs => advanced_template_inputs,
16
19
  }
17
20
  end
18
21
 
@@ -20,6 +23,10 @@ class UiJobWizardController < ::Api::V2::BaseController
20
23
  nested_resource || 'job_template'
21
24
  end
22
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
+
23
30
  def resource_class
24
31
  JobTemplate
25
32
  end
@@ -7,7 +7,9 @@ module ForemanRemoteExecution
7
7
  end
8
8
 
9
9
  def multiple_actions
10
- super + [ [_('Schedule Remote Job'), new_job_invocation_path, false] ]
10
+ res = super
11
+ res += [ [_('Schedule Remote Job'), new_job_invocation_path, false] ] if authorized_for(controller: :job_invocations, action: :new)
12
+ res
11
13
  end
12
14
 
13
15
  def schedule_job_multi_button(*args)
@@ -21,12 +23,14 @@ module ForemanRemoteExecution
21
23
  end
22
24
 
23
25
  def rex_host_features(*args)
26
+ return unless authorized_for(controller: :job_invocations, action: :create)
24
27
  RemoteExecutionFeature.with_host_action_button.order(:label).map do |feature|
25
28
  link_to(_('%s') % feature.name, job_invocations_path(:host_ids => [args.first.id], :feature => feature.label), :method => :post)
26
29
  end
27
30
  end
28
31
 
29
32
  def schedule_job_button(*args)
33
+ return unless authorized_for(controller: :job_invocations, action: :new)
30
34
  link_to(_('Schedule Remote Job'), new_job_invocation_path(:host_ids => [args.first.id]), :id => :run_button, :class => 'btn btn-default')
31
35
  end
32
36
 
@@ -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')
@@ -36,7 +40,7 @@ module RemoteExecutionHelper
36
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
45
  action: { href: foreman_tasks_task_path(host_task),
42
46
  'data-method': 'get', id: "#{host.name}-actions-task" } }
@@ -109,12 +113,14 @@ module RemoteExecutionHelper
109
113
  :class => 'btn btn-danger',
110
114
  :title => _('Try to cancel the job on a host'),
111
115
  :disabled => !task.cancellable?,
112
- :method => :post)
116
+ :method => :post,
117
+ :remote => true)
113
118
  buttons << link_to(_('Abort Job'), abort_foreman_tasks_task_path(task),
114
119
  :class => 'btn btn-danger',
115
120
  :title => _('Try to abort the job on a host without waiting for its result'),
116
121
  :disabled => !task.cancellable?,
117
- :method => :post)
122
+ :method => :post,
123
+ :remote => true)
118
124
  end
119
125
  buttons
120
126
  end
@@ -47,12 +47,16 @@ module Actions
47
47
  script = renderer.render
48
48
  raise _('Failed rendering template: %s') % renderer.error_message unless script
49
49
 
50
+ first_execution = host.executed_through_proxies.where(:id => proxy.id).none?
51
+ host.executed_through_proxies << proxy if first_execution
52
+
50
53
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
51
54
  :script => script,
52
55
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
53
56
  :secrets => secrets(host, job_invocation, provider),
54
57
  :use_batch_triggering => true,
55
- :use_concurrency_control => options[:use_concurrency_control]}
58
+ :use_concurrency_control => options[:use_concurrency_control],
59
+ :first_execution => first_execution }
56
60
  action_options = provider.proxy_command_options(template_invocation, host)
57
61
  .merge(additional_options)
58
62
 
@@ -72,7 +72,7 @@ module Actions
72
72
 
73
73
  def total_count
74
74
  # For compatibility with already existing tasks
75
- return output[:total_count] unless output.has_key?(:host_count) || task.pending?
75
+ return output[:total_count] || hosts.count unless output.has_key?(:host_count) || task.pending?
76
76
 
77
77
  output[:host_count] || hosts.count
78
78
  end
@@ -6,6 +6,8 @@ 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
13
  :ext_method => :search_by_job_invocation,
@@ -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,11 +64,11 @@ class HostStatus::ExecutionStatus < HostStatus::Status
64
64
 
65
65
  case status
66
66
  when OK
67
- [ "foreman_tasks_tasks.state = 'stopped' AND result = 'success'" ]
67
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'success'" ]
68
68
  when CANCELLED
69
- [ "foreman_tasks_tasks.state = 'stopped' AND result = 'cancelled'" ]
69
+ [ "foreman_tasks_tasks.state = 'stopped' AND foreman_tasks_tasks.result = 'cancelled'" ]
70
70
  when ERROR
71
- [ "foreman_tasks_tasks.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
73
  [ "foreman_tasks_tasks.state = 'scheduled' OR foreman_tasks_tasks.state IS NULL" ]
74
74
  when RUNNING
@@ -65,8 +65,6 @@ class JobInvocation < ApplicationRecord
65
65
  scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
66
66
  :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
67
67
 
68
- scope :with_task, -> { references(:task) }
69
-
70
68
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
71
69
 
72
70
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
@@ -99,7 +97,7 @@ class JobInvocation < ApplicationRecord
99
97
  def self.search_by_status(key, operator, value)
100
98
  conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
101
99
  conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
102
- { :conditions => sanitize_sql_for_conditions(conditions), :include => :task }
100
+ { :conditions => sanitize_sql_for_conditions(conditions), :joins => :task }
103
101
  end
104
102
 
105
103
  def self.search_by_recurring_logic(key, operator, value)
@@ -193,11 +191,16 @@ class JobInvocation < ApplicationRecord
193
191
  end
194
192
 
195
193
  def total_hosts_count
194
+ count = _('N/A')
195
+
196
196
  if targeting.resolved?
197
- task&.main_action&.total_count || targeting.hosts.count
198
- else
199
- _('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
200
202
  end
203
+ count
201
204
  end
202
205
 
203
206
  def pattern_template_invocation_for_host(host)
@@ -174,7 +174,7 @@ class JobInvocationComposer
174
174
  input = template.template_inputs_with_foreign.find { |i| i.name == name }
175
175
  unless input
176
176
  raise ::Foreman::Exception, _('Unknown input %{input_name} for template %{template_name}') %
177
- { :input_name => name, :template_name => template.name }
177
+ { :input_name => name, :template_name => template.name }
178
178
  end
179
179
  { :template_input_id => input.id, :value => value }
180
180
  end
@@ -209,7 +209,7 @@ class JobInvocationComposer
209
209
  def format_datetime(datetime)
210
210
  return datetime if datetime.blank?
211
211
 
212
- Time.parse(datetime).strftime('%Y-%m-%d %H:%M')
212
+ Time.parse(datetime).utc.strftime('%Y-%m-%d %H:%M')
213
213
  end
214
214
  end
215
215
 
@@ -296,7 +296,7 @@ class JobInvocationComposer
296
296
  attr_reader :feature_label, :feature, :provided_inputs
297
297
 
298
298
  def initialize(feature_label, hosts, provided_inputs = {})
299
- @feature = RemoteExecutionFeature.feature(feature_label)
299
+ @feature = RemoteExecutionFeature.feature!(feature_label)
300
300
  @provided_inputs = provided_inputs
301
301
  translator = HostIdsTranslator.new(hosts)
302
302
  @host_bookmark = translator.bookmark
@@ -405,7 +405,7 @@ class JobInvocationComposer
405
405
  job_invocation.effective_user_password = params[:effective_user_password]
406
406
 
407
407
  if @reruns && job_invocation.targeting.static?
408
- job_invocation.targeting.host_ids = JobInvocation.find(@reruns).targeting.host_ids
408
+ job_invocation.targeting.assign_host_ids(JobInvocation.find(@reruns).targeting.host_ids)
409
409
  job_invocation.targeting.mark_resolved!
410
410
  end
411
411
 
@@ -20,7 +20,11 @@ class RemoteExecutionFeature < ApplicationRecord
20
20
  end
21
21
 
22
22
  def self.feature(label)
23
- self.find_by(label: label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
23
+ self.find_by(label: label)
24
+ end
25
+
26
+ def self.feature!(label)
27
+ feature(label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
24
28
  end
25
29
 
26
30
  def self.register(label, name, options = {})
@@ -50,7 +50,7 @@ class RemoteExecutionProvider
50
50
  method = host_setting(host, :remote_execution_effective_user_method)
51
51
  unless EFFECTIVE_USER_METHODS.include?(method)
52
52
  raise _('Effective user method "%{current_value}" is not one of %{valid_methods}') %
53
- { :current_value => method, :valid_methods => EFFECTIVE_USER_METHODS}
53
+ { :current_value => method, :valid_methods => EFFECTIVE_USER_METHODS}
54
54
  end
55
55
  method
56
56
  end
@@ -70,13 +70,13 @@ class Setting::RemoteExecution < Setting
70
70
  self.set('remote_execution_form_job_template',
71
71
  N_('Choose a job template that is pre-selected in job invocation form'),
72
72
  'Run Command - SSH Default',
73
- _('Form Job Template'),
73
+ N_('Form Job Template'),
74
74
  nil,
75
75
  { :collection => proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] } }),
76
76
  self.set('remote_execution_job_invocation_report_template',
77
77
  N_('Select a report template used for generating a report for a particular remote execution job'),
78
78
  'Jobs - Invocation report template',
79
- _('Job Invocation Report Template'),
79
+ N_('Job Invocation Report Template'),
80
80
  nil,
81
81
  { :collection => proc { self.job_invocation_report_templates_select } }),
82
82
  ]
@@ -46,9 +46,13 @@ class Targeting < ApplicationRecord
46
46
  # pluck(:id) returns duplicate results for HostCollections
47
47
  host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
48
48
  host_ids.shuffle!(random: Random.new) if randomized_ordering
49
+ self.assign_host_ids(host_ids)
50
+ self.save(:validate => false)
51
+ end
52
+
53
+ def assign_host_ids(host_ids)
49
54
  # this can be optimized even more, by introducing bulk insert
50
55
  self.targeting_hosts.build(host_ids.map { |id| { :host_id => id } })
51
- self.save(:validate => false)
52
56
  end
53
57
 
54
58
  def dynamic?
@@ -21,7 +21,7 @@
21
21
  <% @job_invocations.each do |invocation| %>
22
22
  <tr>
23
23
  <td class="text_warp"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
24
- <td><%= trunc_with_tooltip(invocation&.targeting&.search_query, 15) %></td>
24
+ <td><%= trunc_with_tooltip(invocation.targeting.search_query, 15) %></td>
25
25
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
26
26
  <td><%= invocation_result(invocation, :success_count) %></td>
27
27
  <td><%= invocation_result(invocation, :failed_count) %></td>
@@ -29,6 +29,7 @@ template_inputs:
29
29
  input_type: user
30
30
  required: false
31
31
  advanced: true
32
+ feature: katello_module_stream_action
32
33
  %>
33
34
 
34
35
  <%
@@ -13,6 +13,8 @@ template_inputs:
13
13
  required: true
14
14
  %>
15
15
 
16
+ PATH="$PATH:/usr/sbin:/sbin"
17
+
16
18
  echo <%= input('action') %> host && sleep 3
17
19
  <%= case input('action')
18
20
  when 'restart'
@@ -10,6 +10,7 @@ template_inputs:
10
10
  description: Additional options to pass to puppet
11
11
  input_type: user
12
12
  required: false
13
+ feature: puppet_run_host
13
14
  %>
14
15
  <% if @host.operatingsystem.family == 'Debian' -%>
15
16
  export PATH=/opt/puppetlabs/bin:$PATH
@@ -0,0 +1,12 @@
1
+ class AddHostProxyInvocations < ActiveRecord::Migration[6.0]
2
+ def change
3
+ # rubocop:disable Rails/CreateTableWithTimestamps
4
+ create_table :host_proxy_invocations do |t|
5
+ t.references :host, :null => false
6
+ t.references :smart_proxy, :null => false
7
+ end
8
+ # rubocop:enable Rails/CreateTableWithTimestamps
9
+
10
+ add_index :host_proxy_invocations, [:host_id, :smart_proxy_id], unique: true
11
+ end
12
+ end
@@ -201,12 +201,10 @@ module ForemanRemoteExecution
201
201
 
202
202
  Host::Managed.prepend ForemanRemoteExecution::HostExtensions
203
203
  Host::Managed.include ForemanTasks::Concerns::HostActionSubject
204
- Host::Managed.include ForemanRemoteExecution::Orchestration::SSH
205
204
 
206
205
  (Nic::Base.descendants + [Nic::Base]).each do |klass|
207
206
  klass.send(:include, ForemanRemoteExecution::NicExtensions)
208
207
  end
209
- Nic::Managed.include ForemanRemoteExecution::Orchestration::SSH
210
208
 
211
209
  Bookmark.include ForemanRemoteExecution::BookmarkExtensions
212
210
  HostsHelper.prepend ForemanRemoteExecution::HostsHelperExtensions
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '4.5.0'.freeze
2
+ VERSION = '4.5.4'.freeze
3
3
  end
@@ -864,7 +864,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
864
864
  it 'marks targeting as resolved if static' do
865
865
  created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
866
866
  assert created.targeting.resolved?
867
- assert_equal job_invocation.template_invocations_host_ids, created.targeting.host_ids
867
+ created.targeting.save
868
+ created.targeting.reload
869
+ assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
868
870
  end
869
871
 
870
872
  it 'takes randomized_ordering from the original job invocation when rerunning failed' do
@@ -874,6 +876,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
874
876
  composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
875
877
  assert composer.job_invocation.targeting.randomized_ordering
876
878
  end
879
+
880
+ it 'works with invalid hosts' do
881
+ host = job_invocation.targeting.hosts.first
882
+ ::Host::Managed.any_instance.stubs(:valid?).returns(false)
883
+ composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
884
+ targeting = composer.compose.job_invocation.targeting
885
+ targeting.save!
886
+ targeting.reload
887
+ assert targeting.valid?
888
+ assert_equal targeting.hosts.pluck(:id), [host.id]
889
+ end
877
890
  end
878
891
 
879
892
  describe '.for_feature' do
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
10
10
  end
11
11
 
12
12
  it 'is able to perform search through job invocations' do
13
- found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).with_task.order('job_invocations.id DESC')
13
+ found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
14
14
  _(found_jobs).must_equal [job_invocation]
15
15
  end
16
16
 
@@ -1,3 +1,4 @@
1
+ /* eslint-disable camelcase */
1
2
  import React, { useState, useEffect, useCallback } from 'react';
2
3
  import { useDispatch, useSelector } from 'react-redux';
3
4
  import { Wizard } from '@patternfly/react-core';
@@ -8,6 +9,7 @@ import CategoryAndTemplate from './steps/CategoryAndTemplate/';
8
9
  import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
9
10
  import { JOB_TEMPLATE } from './JobWizardConstants';
10
11
  import { selectTemplateError } from './JobWizardSelectors';
12
+ import Schedule from './steps/Schedule/';
11
13
  import './JobWizard.scss';
12
14
 
13
15
  export const JobWizard = () => {
@@ -16,13 +18,31 @@ export const JobWizard = () => {
16
18
  const [advancedValues, setAdvancedValues] = useState({});
17
19
  const dispatch = useDispatch();
18
20
 
19
- const setDefaults = useCallback(response => {
20
- const responseJob = response.data;
21
- setAdvancedValues({
22
- effectiveUserValue: responseJob.effective_user?.value || '',
23
- timeoutToKill: responseJob.job_template.execution_timeout_interval || '',
24
- });
25
- }, []);
21
+ const setDefaults = useCallback(
22
+ ({
23
+ data: {
24
+ advanced_template_inputs,
25
+ effective_user,
26
+ job_template: { executionTimeoutInterval, description_format },
27
+ },
28
+ }) => {
29
+ const advancedTemplateValues = {};
30
+ const advancedInputs = advanced_template_inputs;
31
+ if (advancedInputs) {
32
+ advancedInputs.forEach(input => {
33
+ advancedTemplateValues[input.name] = input?.default || '';
34
+ });
35
+ }
36
+ setAdvancedValues(currentAdvancedValues => ({
37
+ ...currentAdvancedValues,
38
+ effectiveUserValue: effective_user?.value || '',
39
+ timeoutToKill: executionTimeoutInterval || '',
40
+ templateValues: advancedTemplateValues,
41
+ description: description_format || '',
42
+ }));
43
+ },
44
+ []
45
+ );
26
46
  useEffect(() => {
27
47
  if (jobTemplateID) {
28
48
  dispatch(
@@ -72,7 +92,7 @@ export const JobWizard = () => {
72
92
  },
73
93
  {
74
94
  name: __('Schedule'),
75
- component: <p>Schedule</p>,
95
+ component: <Schedule />,
76
96
  canJumpTo: isTemplate,
77
97
  },
78
98
  {