foreman_remote_execution 7.2.2 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -2
  3. data/app/controllers/job_invocations_controller.rb +1 -2
  4. data/app/controllers/job_templates_controller.rb +1 -1
  5. data/app/controllers/ui_job_wizard_controller.rb +15 -0
  6. data/app/helpers/job_invocations_helper.rb +0 -7
  7. data/app/helpers/remote_execution_helper.rb +2 -2
  8. data/app/lib/actions/remote_execution/proxy_action.rb +46 -0
  9. data/app/lib/actions/remote_execution/run_host_job.rb +38 -11
  10. data/app/lib/actions/remote_execution/run_hosts_job.rb +7 -6
  11. data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +27 -0
  12. data/app/models/job_invocation.rb +1 -7
  13. data/app/models/job_invocation_composer.rb +9 -2
  14. data/app/models/remote_execution_provider.rb +9 -1
  15. data/app/models/ssh_execution_provider.rb +1 -0
  16. data/app/models/template_invocation.rb +1 -0
  17. data/app/models/template_invocation_event.rb +11 -0
  18. data/app/views/job_invocations/_form.html.erb +4 -0
  19. data/app/views/job_invocations/new.html.erb +5 -0
  20. data/app/views/templates/script/package_action.erb +8 -3
  21. data/config/routes.rb +6 -4
  22. data/db/migrate/20220713095705_create_template_invocation_events.rb +17 -0
  23. data/db/migrate/20220822155946_add_time_to_pickup_to_job_invocation.rb +5 -0
  24. data/extra/cockpit/foreman-cockpit-session +303 -230
  25. data/extra/cockpit/foreman-cockpit.service +1 -0
  26. data/foreman_remote_execution.gemspec +1 -1
  27. data/lib/foreman_remote_execution/engine.rb +16 -11
  28. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +131 -0
  29. data/lib/foreman_remote_execution/version.rb +1 -1
  30. data/test/functional/api/v2/job_invocations_controller_test.rb +8 -0
  31. data/test/helpers/remote_execution_helper_test.rb +4 -0
  32. data/test/unit/job_invocation_report_template_test.rb +1 -1
  33. data/test/unit/job_invocation_test.rb +1 -2
  34. data/webpack/JobWizard/JobWizard.js +201 -32
  35. data/webpack/JobWizard/JobWizard.scss +46 -1
  36. data/webpack/JobWizard/JobWizardConstants.js +12 -2
  37. data/webpack/JobWizard/JobWizardHelpers.js +15 -0
  38. data/webpack/JobWizard/JobWizardPageRerun.js +136 -0
  39. data/webpack/JobWizard/JobWizardSelectors.js +8 -2
  40. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +84 -0
  41. data/webpack/JobWizard/__tests__/fixtures.js +99 -2
  42. data/webpack/JobWizard/__tests__/integration.test.js +17 -3
  43. data/webpack/JobWizard/autofill.js +36 -7
  44. data/webpack/JobWizard/index.js +25 -6
  45. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +25 -0
  46. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +12 -1
  47. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +41 -6
  48. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +37 -18
  49. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +1 -1
  50. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
  51. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +6 -2
  52. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +31 -23
  53. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +32 -0
  54. data/webpack/JobWizard/steps/HostsAndInputs/index.js +2 -2
  55. data/webpack/JobWizard/steps/ReviewDetails/index.js +2 -3
  56. data/webpack/JobWizard/steps/Schedule/PurposeField.js +1 -3
  57. data/webpack/JobWizard/steps/Schedule/QueryType.js +33 -40
  58. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +55 -16
  59. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +19 -56
  60. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +1 -1
  61. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +126 -0
  62. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +287 -0
  63. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +88 -20
  64. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +206 -186
  65. data/webpack/JobWizard/steps/form/DateTimePicker.js +23 -6
  66. data/webpack/JobWizard/steps/form/FormHelpers.js +21 -1
  67. data/webpack/JobWizard/steps/form/Formatter.js +29 -14
  68. data/webpack/JobWizard/steps/form/ResourceSelect.js +97 -10
  69. data/webpack/JobWizard/steps/form/SearchSelect.js +2 -2
  70. data/webpack/JobWizard/steps/form/SelectField.js +4 -0
  71. data/webpack/JobWizard/submit.js +11 -4
  72. data/webpack/JobWizard/validation.js +1 -0
  73. data/webpack/Routes/routes.js +7 -1
  74. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +1 -0
  75. data/webpack/react_app/components/FeaturesDropdown/actions.js +23 -2
  76. data/webpack/react_app/components/FeaturesDropdown/index.js +2 -0
  77. data/webpack/react_app/extend/Fills.js +1 -1
  78. metadata +16 -7
  79. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -106
  80. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -32
  81. data/webpack/JobWizard/steps/Schedule/index.js +0 -178
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7d32320885b2dc2343cf6efc7c1f7b0f4ac1e0668ea3d0c2dc817c691f1e7d2
4
- data.tar.gz: 0655a041907bc30339ac53479220adb5434b20f7e6f38d5ce843dfb675f375cd
3
+ metadata.gz: 9a4cbf1cdf74f0bd20c4729a8bc05a7a3bb2a958bace18d30755fb600d0a999c
4
+ data.tar.gz: 7eac3c732b1c9b05e9c0909d952b859010cdeef677324353901859232d8ab3b0
5
5
  SHA512:
6
- metadata.gz: 307bb3022d6fca4c61897443448638d84069ce943abb08d63b4e7de99cebdcf4f9956b9aed33c6e3effe2dad9a897c8981e80c775866ea5f32081ddf8950606d
7
- data.tar.gz: b32a13df6917c8d41832b66dce88ecdc55383e296e250cbdecfb935f162b5e6e42fb694d57a7338ef6c5c102be9c6a6d2c2e95e1b04df9ac7bfa115b469364c1
6
+ metadata.gz: 42280d6aadec2d5ede443a1c81f688ce838bf388a65b4abdd30ab3d03953c33986023f0aee445e06cf908b5d6040e716a7bc8582b6059f553f400a4328ace60a
7
+ data.tar.gz: 3248ccc1bc2f48d61d3538f171d8f6c005b241c654ca2949833993896489bbc4d2ca2c2e0f7f0776dbd6d352f63d0fc2a292edb29f1df3cf6d402ffc16feb1c8
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Setup Ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: 2.5
19
+ ruby-version: 2.7
20
20
  bundler-cache: true
21
21
  cache-version: 1
22
22
  rubygems: 3.0.0
@@ -38,7 +38,7 @@ jobs:
38
38
  fail-fast: false
39
39
  matrix:
40
40
  foreman-core-branch: [develop]
41
- ruby-version: [2.5, 2.7]
41
+ ruby-version: [2.7]
42
42
  node-version: [12]
43
43
  steps:
44
44
  - run: sudo apt-get update
@@ -45,8 +45,7 @@ class JobInvocationsController < ApplicationController
45
45
  if @composer.trigger
46
46
  redirect_to job_invocation_path(@composer.job_invocation)
47
47
  else
48
- @composer.job_invocation.description_format = nil if params.fetch(:job_invocation, {}).key?(:description_override)
49
- render :action => 'new'
48
+ redirect_to new_job_invocation_path({:inputs => params[:inputs], :feature => params[:feature], :host_ids => params[:host_ids]})
50
49
  end
51
50
  end
52
51
 
@@ -34,7 +34,7 @@ class JobTemplatesController < ::TemplatesController
34
34
  def import
35
35
  contents = params.fetch(:imported_template, {}).fetch(:template, nil).try(:read)
36
36
 
37
- @template = JobTemplate.import_raw(contents, :update => Foreman::Cast.to_bool(params[:imported_template][:overwrite]))
37
+ @template = JobTemplate.import_raw(contents, :update => ActiveRecord::Type::Boolean.new.deserialize(params[:imported_template][:overwrite]))
38
38
  if @template&.save
39
39
  flash[:success] = _('Job template imported successfully.')
40
40
  redirect_to job_templates_path(:search => "name = \"#{@template.name}\"")
@@ -57,4 +57,19 @@ class UiJobWizardController < ApplicationController
57
57
  render :json => { :results =>
58
58
  resource_list.sort_by { |r| r[:name] }.take(100), :subtotal => resource_list.count}
59
59
  end
60
+
61
+ def job_invocation
62
+ job = JobInvocation.authorized.find(params[:id])
63
+ composer = JobInvocationComposer.from_job_invocation(job, params).params
64
+ job_template_inputs = JobTemplate.authorized.find(composer[:template_invocations][0][:template_id]).template_inputs_with_foreign
65
+ inputs = Hash[job_template_inputs.map { |input| ["inputs[#{input[:name]}]", {:advanced => input[:advanced], :value => (composer[:template_invocations][0][:input_values].find { |value| value[:template_input_id] == input[:id] }).try(:[], :value)}] }]
66
+ job_organization = Taxonomy.find_by(id: job.task.input[:current_organization_id])
67
+ job_location = Taxonomy.find_by(id: job.task.input[:current_location_id])
68
+ render :json => {
69
+ :job => composer,
70
+ :job_organization => job_organization,
71
+ :job_location => job_location,
72
+ :inputs => inputs,
73
+ }
74
+ end
60
75
  end
@@ -13,13 +13,6 @@ module JobInvocationsHelper
13
13
  end
14
14
  end
15
15
 
16
- def job_invocations_buttons
17
- [
18
- documentation_button_rex('3.2ExecutingaJob'),
19
- display_link_if_authorized(_('Run Job'), hash_for_new_job_invocation_path),
20
- ]
21
- end
22
-
23
16
  def template_name_and_provider_link(template)
24
17
  template_name = content_tag(:strong, template.name)
25
18
  provider = template.provider.humanized_name
@@ -56,7 +56,7 @@ module RemoteExecutionHelper
56
56
  def job_invocations_buttons
57
57
  [
58
58
  documentation_button_rex('3.2ExecutingaJob'),
59
- new_link(_('Run Job')),
59
+ display_link_if_authorized(_('Run Job'), hash_for_new_job_invocation_path, {:class => "btn btn-primary"}),
60
60
  ]
61
61
  end
62
62
 
@@ -246,7 +246,7 @@ module RemoteExecutionHelper
246
246
  def job_report_template
247
247
  template = ReportTemplate.where(name: Setting['remote_execution_job_invocation_report_template']).first
248
248
 
249
- template if template.template_inputs.where(name: 'job_id').exists?
249
+ template if template && template.template_inputs.where(name: 'job_id').exists?
250
250
  end
251
251
 
252
252
  def job_report_template_parameters(job_invocation, template)
@@ -0,0 +1,46 @@
1
+ module Actions
2
+ module RemoteExecution
3
+ class ProxyAction < ::Actions::ProxyAction
4
+ include Actions::RemoteExecution::TemplateInvocationProgressLogging
5
+
6
+ def on_data(data, meta = {})
7
+ super
8
+ process_proxy_data(output[:proxy_output])
9
+ end
10
+
11
+ def run(event = nil)
12
+ with_template_invocation_error_logging { super }
13
+ end
14
+
15
+ private
16
+
17
+ def get_proxy_data(response)
18
+ data = super
19
+ process_proxy_data(data)
20
+ data
21
+ end
22
+
23
+ def process_proxy_data(data)
24
+ events = data['result'].map do |update|
25
+ {
26
+ template_invocation_id: template_invocation.id,
27
+ event: update['output'],
28
+ timestamp: Time.at(update['timestamp']).getlocal,
29
+ event_type: update['output_type'],
30
+ }
31
+ end
32
+ if data['exit_status']
33
+ events << {
34
+ template_invocation_id: template_invocation.id,
35
+ event: data['exit_status'],
36
+ timestamp: events.last[:timestamp] + 0.0001,
37
+ event_type: 'exit',
38
+ }
39
+ end
40
+ events.each_slice(1000) do |batch|
41
+ TemplateInvocationEvent.upsert_all(batch, unique_by: [:template_invocation_id, :timestamp, :event_type]) # rubocop:disable Rails/SkipsModelValidations
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -4,6 +4,7 @@ module Actions
4
4
  include ::Actions::Helpers::WithContinuousOutput
5
5
  include ::Actions::Helpers::WithDelegatedAction
6
6
  include ::Actions::ObservableAction
7
+ include ::Actions::RemoteExecution::TemplateInvocationProgressLogging
7
8
 
8
9
  execution_plan_hooks.use :emit_feature_event, :on => :success
9
10
 
@@ -19,6 +20,13 @@ module Actions
19
20
  end
20
21
 
21
22
  def plan(job_invocation, host, template_invocation, proxy_selector = ::RemoteExecutionProxySelector.new, options = {})
23
+ with_template_invocation_error_logging do
24
+ inner_plan(job_invocation, host, template_invocation, proxy_selector, options)
25
+ end
26
+ end
27
+
28
+ def inner_plan(job_invocation, host, template_invocation, proxy_selector, options)
29
+ raise _('Could not use any template used in the job invocation') if template_invocation.blank?
22
30
  features = template_invocation.template.remote_execution_features.pluck(:label).uniq
23
31
  action_subject(host,
24
32
  :job_category => job_invocation.job_category,
@@ -35,8 +43,6 @@ module Actions
35
43
 
36
44
  verify_permissions(host, template_invocation)
37
45
 
38
- raise _('Could not use any template used in the job invocation') if template_invocation.blank?
39
-
40
46
  provider = template_invocation.template.provider
41
47
  proxy_selector = provider.required_proxy_selector_for(template_invocation.template) || proxy_selector
42
48
 
@@ -63,13 +69,15 @@ module Actions
63
69
  action_options = provider.proxy_command_options(template_invocation, host)
64
70
  .merge(additional_options)
65
71
 
66
- plan_delegated_action(proxy, provider.proxy_action_class, action_options)
67
- plan_self
72
+ plan_delegated_action(proxy, provider.proxy_action_class, action_options, proxy_action_class: ::Actions::RemoteExecution::ProxyAction)
73
+ plan_self :with_event_logging => true
68
74
  end
69
75
 
70
76
  def finalize(*args)
71
- update_host_status
72
- check_exit_status
77
+ with_template_invocation_error_logging do
78
+ update_host_status
79
+ check_exit_status
80
+ end
73
81
  end
74
82
 
75
83
  def self.feature_job_event_name(label, suffix = :success)
@@ -128,6 +136,11 @@ module Actions
128
136
  end
129
137
 
130
138
  def fill_continuous_output(continuous_output)
139
+ if input[:with_event_logging]
140
+ continuous_output_from_template_invocation_events(continuous_output)
141
+ return
142
+ end
143
+
131
144
  delegated_output.fetch('result', []).each do |raw_output|
132
145
  continuous_output.add_raw_output(raw_output)
133
146
  end
@@ -148,8 +161,26 @@ module Actions
148
161
  continuous_output.add_exception(_('Error loading data from proxy'), e)
149
162
  end
150
163
 
164
+ def continuous_output_from_template_invocation_events(continuous_output)
165
+ begin
166
+ # Trigger reload
167
+ delegated_output unless task.state == 'stopped'
168
+ rescue => e
169
+ # This is enough, the error will get shown using add_exception at the end of the method
170
+ end
171
+
172
+ task.template_invocation.template_invocation_events.find_each do |output|
173
+ if output.event_type == 'exit'
174
+ continuous_output.add_output(_('Exit status: %s') % output.event, 'stdout', output.timestamp)
175
+ else
176
+ continuous_output.add_raw_output(output.as_raw_continuous_output)
177
+ end
178
+ end
179
+ continuous_output.add_exception(_('Error loading data from proxy'), e) if e
180
+ end
181
+
151
182
  def exit_status
152
- delegated_output[:exit_status]
183
+ input[:with_event_logging] ? task.template_invocation.template_invocation_events.find_by(event_type: 'exit').event : delegated_output[:exit_status]
153
184
  end
154
185
 
155
186
  def host_id
@@ -172,10 +203,6 @@ module Actions
172
203
  @host ||= ::Host.authorized.find(host_id)
173
204
  end
174
205
 
175
- def self.cleanup_after
176
- '90d'
177
- end
178
-
179
206
  private
180
207
 
181
208
  def update_host_status
@@ -47,7 +47,6 @@ module Actions
47
47
  # composer creates just "pattern" for template_invocations because target is evaluated
48
48
  # during actual run (here) so we build template invocations from these patterns
49
49
  template_invocation = job_invocation.pattern_template_invocation_for_host(host).deep_clone
50
- template_invocation.host_id = host.id
51
50
  trigger(RunHostJob, job_invocation, host, template_invocation, proxy_selector, { :use_concurrency_control => uses_concurrency_control })
52
51
  end
53
52
  end
@@ -76,7 +75,7 @@ module Actions
76
75
  job_invocation.save!
77
76
 
78
77
  Rails.logger.debug "cleaning cache for keys that begin with 'job_invocation_#{job_invocation.id}'"
79
- Rails.cache.delete_matched(/\A#{JobInvocation::CACHE_PREFIX}_#{job_invocation.id}/)
78
+ Rails.cache.delete_matched(cache_deletion_query(job_invocation.id))
80
79
  end
81
80
 
82
81
  def notify_on_success(_plan)
@@ -153,15 +152,17 @@ module Actions
153
152
  input[:proxy_batch_size]
154
153
  end
155
154
 
156
- def self.cleanup_after
157
- '90d'
158
- end
159
-
160
155
  private
161
156
 
162
157
  def mail_notification_preference
163
158
  UserMailNotification.where(mail_notification_id: RexMailNotification.first, user_id: User.current.id).first
164
159
  end
160
+
161
+ def cache_deletion_query(job_invocation_id)
162
+ return "#{JobInvocation::CACHE_PREFIX}_#{job_invocation_id}*" if Rails.cache.kind_of? ActiveSupport::Cache::RedisCacheStore
163
+
164
+ /\A#{JobInvocation::CACHE_PREFIX}_#{job_invocation_id}/
165
+ end
165
166
  end
166
167
  end
167
168
  end
@@ -0,0 +1,27 @@
1
+ module Actions
2
+ module RemoteExecution
3
+ module TemplateInvocationProgressLogging
4
+ def template_invocation
5
+ @template_invocation ||= TemplateInvocation.find_by(:run_host_job_task_id => task.id)
6
+ end
7
+
8
+ def log_template_invocation_exception(exception)
9
+ template_invocation.template_invocation_events.create!(
10
+ :event_type => 'debug',
11
+ :event => "#{exception.class}: #{exception.message}",
12
+ :timestamp => Time.zone.now
13
+ )
14
+ end
15
+
16
+ def with_template_invocation_error_logging
17
+ unless catch(::Dynflow::Action::ERROR) { yield || true }
18
+ log_template_invocation_exception(error.exception)
19
+ throw ::Dynflow::Action::ERROR
20
+ end
21
+ rescue => e # rubocop:disable Style/RescueStandardError
22
+ log_template_invocation_exception(e)
23
+ raise e
24
+ end
25
+ end
26
+ end
27
+ end
@@ -213,13 +213,7 @@ class JobInvocation < ApplicationRecord
213
213
 
214
214
  def pattern_template_invocation_for_host(host)
215
215
  providers = available_providers(host)
216
- providers.each do |provider|
217
- pattern_template_invocations.each do |template_invocation|
218
- if template_invocation.template.provider_type == provider
219
- return template_invocation
220
- end
221
- end
222
- end
216
+ pattern_template_invocations.find { |pti| providers.include? pti.template.provider_type }
223
217
  end
224
218
 
225
219
  # TODO: determine from the host and job_invocation details
@@ -22,6 +22,7 @@ class JobInvocationComposer
22
22
  :effective_user_password => blank_to_nil(job_invocation_base[:effective_user_password]),
23
23
  :concurrency_control => concurrency_control_params,
24
24
  :execution_timeout_interval => execution_timeout_interval,
25
+ :time_to_pickup => job_invocation_base[:time_to_pickup],
25
26
  :template_invocations => template_invocations_params }.with_indifferent_access
26
27
  end
27
28
 
@@ -129,6 +130,7 @@ class JobInvocationComposer
129
130
  :key_passphrase => api_params[:key_passphrase],
130
131
  :concurrency_control => concurrency_control_params,
131
132
  :execution_timeout_interval => api_params[:execution_timeout_interval] || template.execution_timeout_interval,
133
+ :time_to_pickup => api_params[:time_to_pickup],
132
134
  :template_invocations => template_invocations_params }.with_indifferent_access
133
135
  end
134
136
 
@@ -245,6 +247,7 @@ class JobInvocationComposer
245
247
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
246
248
  :remote_execution_feature_id => job_invocation.remote_execution_feature_id,
247
249
  :template_invocations => template_invocations_params,
250
+ :time_to_pickup => job_invocation.time_to_pickup,
248
251
  :reruns => job_invocation.id }.with_indifferent_access
249
252
  end
250
253
 
@@ -415,6 +418,7 @@ class JobInvocationComposer
415
418
  job_invocation.key_passphrase = params[:key_passphrase]
416
419
  job_invocation.effective_user_password = params[:effective_user_password]
417
420
  job_invocation.ssh_user = params[:ssh_user]
421
+ job_invocation.time_to_pickup = params[:time_to_pickup]
418
422
 
419
423
  if @reruns && job_invocation.targeting.static?
420
424
  job_invocation.targeting.assign_host_ids(JobInvocation.find(@reruns).targeting.host_ids)
@@ -441,8 +445,11 @@ class JobInvocationComposer
441
445
  end
442
446
 
443
447
  def valid?
444
- targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false) &
445
- triggering.valid?
448
+ unless triggering.valid?
449
+ job_invocation.errors.add(:triggering, 'is invalid')
450
+ return false
451
+ end
452
+ targeting.valid? & job_invocation.valid? & !pattern_template_invocations.map(&:valid?).include?(false)
446
453
  end
447
454
 
448
455
  def save
@@ -33,7 +33,10 @@ class RemoteExecutionProvider
33
33
  end
34
34
 
35
35
  def proxy_command_options(template_invocation, host)
36
- {:proxy_operation_name => proxy_operation_name}.merge(proxy_command_provider_inputs(template_invocation))
36
+ {
37
+ :proxy_operation_name => proxy_operation_name,
38
+ :time_to_pickup => time_to_pickup(template_invocation.job_invocation, host),
39
+ }.merge(proxy_command_provider_inputs(template_invocation))
37
40
  end
38
41
 
39
42
  def secrets(_host)
@@ -152,5 +155,10 @@ class RemoteExecutionProvider
152
155
  def alternative_names(host)
153
156
  { :fqdn => find_fqdn(effective_interfaces(host)) }
154
157
  end
158
+
159
+ def time_to_pickup(job_invocation, host)
160
+ time = job_invocation.time_to_pickup || host_setting(host, 'remote_execution_time_to_pickup')
161
+ Integer(time) if time
162
+ end
155
163
  end
156
164
  end
@@ -45,6 +45,7 @@ class ScriptExecutionProvider < RemoteExecutionProvider
45
45
  :ssh_port => ssh_port(host),
46
46
  :ssh_password => ssh_password(host),
47
47
  :ssh_key_passphrase => ssh_key_passphrase(host),
48
+ :effective_user_password => effective_user_password(host),
48
49
  }
49
50
  end
50
51
 
@@ -17,6 +17,7 @@ class TemplateInvocation < ApplicationRecord
17
17
  has_one :host_group, :through => :host, :source => :hostgroup
18
18
  belongs_to :run_host_job_task, :class_name => 'ForemanTasks::Task'
19
19
  has_many :remote_execution_features, :through => :template
20
+ has_many :template_invocation_events, :dependent => :destroy
20
21
 
21
22
  validates_associated :input_values
22
23
  validate :provides_required_input_values
@@ -0,0 +1,11 @@
1
+ class TemplateInvocationEvent < ::ApplicationRecord
2
+ belongs_to :template_invocation
3
+
4
+ def as_raw_continuous_output
5
+ raw = attributes
6
+ raw['output_type'] = raw.delete('event_type')
7
+ raw['output'] = raw.delete('event')
8
+ raw['timestamp'] = raw['timestamp'].to_f
9
+ raw
10
+ end
11
+ end
@@ -102,6 +102,10 @@
102
102
  </fieldset>
103
103
  <% end %>
104
104
 
105
+ <div class="advanced hidden">
106
+ <%= number_f f, :time_to_pickup, :value => f.object.time_to_pickup, :label => _('Time to pickup'), :label_help => N_('Interval in seconds, if the job is not picked up by a client within this interval it will be cancelled.') %>
107
+ </div>
108
+
105
109
  <div class="advanced hidden">
106
110
  <%= password_f f, :password, :placeholder => '*****', :label => _('Password'), :label_help => N_('Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
107
111
  <%= password_f f, :key_passphrase, :placeholder => '*****', :label => _('Private key passphrase'), :label_help => N_('Key passhprase is only applicable for SSH provider. Other providers ignore this field. <br> Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.') %>
@@ -1,4 +1,9 @@
1
1
  <% javascript 'foreman_remote_execution/template_invocation' %>
2
2
  <% stylesheet 'foreman_remote_execution/foreman_remote_execution' %>
3
3
  <% title _('Job invocation') %>
4
+ <% if(request.path_parameters[:action] == "rerun") %>
5
+ <%= display_link_if_authorized(_('Use new job wizard'), hash_for_rerun_job_invocation_path({:id => request.path_parameters[:id]}).merge(request.query_parameters)) %>
6
+ <% else %>
7
+ <%= display_link_if_authorized(_('Use new job wizard'), hash_for_new_job_invocation_path().merge(request.query_parameters)) %>
8
+ <%end%>
4
9
  <%= render :partial => 'form' %>
@@ -16,6 +16,11 @@ template_inputs:
16
16
  input_type: user
17
17
  required: true
18
18
  options: "install\nupdate\nremove\ngroup install\ngroup remove"
19
+ - name: options
20
+ description: Additional options for the package manager
21
+ input_type: user
22
+ required: false
23
+ advanced: true
19
24
  - name: package
20
25
  description: The name of the package, if any
21
26
  input_type: user
@@ -87,7 +92,7 @@ handle_zypp_res_codes () {
87
92
 
88
93
  # Action
89
94
  <% if package_manager == 'yum' -%>
90
- yum -y <%= action %> <%= input("package") %>
95
+ yum -y <%= input("options") %> <%= action %> <%= input("package") %>
91
96
  <% elsif package_manager == 'apt' -%>
92
97
  <%-
93
98
  action = 'install' if action == 'group install'
@@ -103,7 +108,7 @@ handle_zypp_res_codes () {
103
108
  [ -x "$(command -v subscription-manager)" ] && subscription-manager refresh
104
109
  export DEBIAN_FRONTEND=noninteractive
105
110
  apt-get -y update
106
- apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= action %> <%= input("package") %>
111
+ apt-get -o APT::Get::Upgrade-Allow-New="true" -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= input("options") %> <%= action %> <%= input("package") %>
107
112
  <% elsif package_manager == 'zypper' -%>
108
113
  <%-
109
114
  if action == "group install"
@@ -113,7 +118,7 @@ handle_zypp_res_codes () {
113
118
  end
114
119
  -%>
115
120
  zypper refresh
116
- zypper -n <%= action %> <%= input("package") %>
121
+ zypper -n <%= action %> <%= input("options") %> <%= input("package") %>
117
122
  handle_zypp_res_codes $?
118
123
  <% end -%>
119
124
  RETVAL=$?
data/config/routes.rb CHANGED
@@ -16,7 +16,11 @@ Rails.application.routes.draw do
16
16
  end
17
17
  end
18
18
 
19
- match 'job_invocations/new', to: 'job_invocations#new', via: [:get, :post], as: 'new_job_invocation'
19
+ match 'job_invocations/new', to: 'react#index', :via => [:get], as: 'new_job_invocation'
20
+ match 'job_invocations/new', to: 'job_invocations#create', via: [:post], as: 'create_job_invocation'
21
+ match 'job_invocations/:id/rerun', to: 'react#index', :via => [:get], as: 'rerun_job_invocation'
22
+ match 'old/job_invocations/new', to: 'job_invocations#new', via: [:get], as: 'form_new_job_invocation'
23
+ match 'old/job_invocations/:id/rerun', to: 'job_invocations#rerun', via: [:get, :post], as: 'form_rerun_job_invocation'
20
24
  resources :job_invocations, :only => [:create, :show, :index] do
21
25
  collection do
22
26
  post 'refresh'
@@ -25,7 +29,6 @@ Rails.application.routes.draw do
25
29
  get 'auto_complete_search'
26
30
  end
27
31
  member do
28
- get 'rerun'
29
32
  post 'cancel'
30
33
  end
31
34
  end
@@ -46,8 +49,7 @@ Rails.application.routes.draw do
46
49
  get 'ui_job_wizard/categories', to: 'ui_job_wizard#categories'
47
50
  get 'ui_job_wizard/template/:id', to: 'ui_job_wizard#template'
48
51
  get 'ui_job_wizard/resources', to: 'ui_job_wizard#resources'
49
-
50
- match '/experimental/job_wizard', to: 'react#index', :via => [:get]
52
+ get 'ui_job_wizard/job_invocation', to: 'ui_job_wizard#job_invocation'
51
53
 
52
54
  namespace :api, :defaults => {:format => 'json'} do
53
55
  scope '(:apiv)', :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
@@ -0,0 +1,17 @@
1
+ class CreateTemplateInvocationEvents < ActiveRecord::Migration[6.1]
2
+ def change
3
+ # rubocop:disable Rails/CreateTableWithTimestamps
4
+ create_table :template_invocation_events do |t|
5
+ t.references :template_invocation, :null => false
6
+ t.timestamp :timestamp, :null => false
7
+ t.string :event_type, :null => false
8
+ t.string :event, :null => false
9
+ t.string :meta
10
+
11
+ t.index [:template_invocation_id, :timestamp, :event_type],
12
+ unique: true,
13
+ name: 'unique_template_invocation_events_index'
14
+ end
15
+ # rubocop:enable Rails/CreateTableWithTimestamps
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ class AddTimeToPickupToJobInvocation < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :job_invocations, :time_to_pickup, :integer
4
+ end
5
+ end