foreman_remote_execution 8.0.0 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/job_invocations_controller.rb +1 -2
  3. data/app/controllers/job_templates_controller.rb +1 -1
  4. data/app/controllers/ui_job_wizard_controller.rb +1 -1
  5. data/app/helpers/job_invocations_helper.rb +0 -7
  6. data/app/helpers/remote_execution_helper.rb +1 -1
  7. data/app/lib/actions/remote_execution/proxy_action.rb +46 -0
  8. data/app/lib/actions/remote_execution/run_host_job.rb +38 -11
  9. data/app/lib/actions/remote_execution/run_hosts_job.rb +7 -6
  10. data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +27 -0
  11. data/app/models/job_invocation.rb +5 -9
  12. data/app/models/job_invocation_composer.rb +4 -0
  13. data/app/models/remote_execution_provider.rb +10 -2
  14. data/app/models/ssh_execution_provider.rb +1 -0
  15. data/app/models/template_invocation.rb +1 -0
  16. data/app/models/template_invocation_event.rb +11 -0
  17. data/app/views/job_invocations/_form.html.erb +4 -0
  18. data/app/views/job_invocations/new.html.erb +5 -0
  19. data/app/views/templates/script/package_action.erb +1 -1
  20. data/config/routes.rb +5 -5
  21. data/db/migrate/20220713095705_create_template_invocation_events.rb +17 -0
  22. data/db/migrate/20220822155946_add_time_to_pickup_to_job_invocation.rb +5 -0
  23. data/extra/cockpit/foreman-cockpit-session +303 -230
  24. data/extra/cockpit/foreman-cockpit.service +1 -0
  25. data/foreman_remote_execution.gemspec +1 -1
  26. data/lib/foreman_remote_execution/engine.rb +12 -7
  27. data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +131 -0
  28. data/lib/foreman_remote_execution/version.rb +1 -1
  29. data/test/unit/remote_execution_provider_test.rb +22 -0
  30. data/webpack/JobWizard/JobWizard.js +53 -18
  31. data/webpack/JobWizard/JobWizard.scss +3 -0
  32. data/webpack/JobWizard/JobWizardConstants.js +1 -1
  33. data/webpack/JobWizard/JobWizardHelpers.js +15 -0
  34. data/webpack/JobWizard/JobWizardPageRerun.js +29 -5
  35. data/webpack/JobWizard/JobWizardSelectors.js +8 -2
  36. data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +5 -0
  37. data/webpack/JobWizard/__tests__/fixtures.js +26 -2
  38. data/webpack/JobWizard/autofill.js +32 -10
  39. data/webpack/JobWizard/index.js +25 -6
  40. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +25 -0
  41. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +12 -1
  42. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +41 -6
  43. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +1 -1
  44. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +1 -1
  45. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
  46. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +6 -2
  47. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +28 -20
  48. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +32 -0
  49. data/webpack/JobWizard/steps/HostsAndInputs/index.js +2 -2
  50. data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -0
  51. data/webpack/JobWizard/steps/form/FormHelpers.js +21 -1
  52. data/webpack/JobWizard/steps/form/Formatter.js +22 -6
  53. data/webpack/JobWizard/steps/form/ResourceSelect.js +97 -10
  54. data/webpack/JobWizard/steps/form/SearchSelect.js +2 -2
  55. data/webpack/JobWizard/steps/form/SelectField.js +4 -0
  56. data/webpack/JobWizard/submit.js +3 -1
  57. data/webpack/JobWizard/validation.js +1 -0
  58. data/webpack/Routes/routes.js +3 -3
  59. data/webpack/react_app/components/FeaturesDropdown/actions.js +23 -2
  60. data/webpack/react_app/components/FeaturesDropdown/index.js +2 -0
  61. data/webpack/react_app/components/HostKebab/KebabItems.js +1 -0
  62. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +5 -0
  63. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +51 -59
  64. data/webpack/react_app/extend/Fills.js +3 -3
  65. metadata +12 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3c6d67549990da3bbd1d7cbf55408bd4a1dc4d7fc5cbcec8db99eded747964
4
- data.tar.gz: ca4e519773be6b0493341fd25ba55e8d4345367c88e0b068183ebe66d020e22e
3
+ metadata.gz: 9a4cbf1cdf74f0bd20c4729a8bc05a7a3bb2a958bace18d30755fb600d0a999c
4
+ data.tar.gz: 7eac3c732b1c9b05e9c0909d952b859010cdeef677324353901859232d8ab3b0
5
5
  SHA512:
6
- metadata.gz: bbeb56766cc65ed9fcd587f260fb29fca965b961b0787230f0510166702351cb5ff0c78387b3f818b9741f281519f33931bc8d0aa36a923d92e0d50f620d113e
7
- data.tar.gz: 3238dc8bc7cdbaa361a6c077e5d611839cb549424ad98daea9e68ac81a932dd6c8de2f233b31f019cc7a1d037b6fea4a656ca3d7e789eb9a794170fb26ac1f68
6
+ metadata.gz: 42280d6aadec2d5ede443a1c81f688ce838bf388a65b4abdd30ab3d03953c33986023f0aee445e06cf908b5d6040e716a7bc8582b6059f553f400a4328ace60a
7
+ data.tar.gz: 3248ccc1bc2f48d61d3538f171d8f6c005b241c654ca2949833993896489bbc4d2ca2c2e0f7f0776dbd6d352f63d0fc2a292edb29f1df3cf6d402ffc16feb1c8
@@ -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}\"")
@@ -62,7 +62,7 @@ class UiJobWizardController < ApplicationController
62
62
  job = JobInvocation.authorized.find(params[:id])
63
63
  composer = JobInvocationComposer.from_job_invocation(job, params).params
64
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]}]", (composer[:template_invocations][0][:input_values].find { |value| value[:template_input_id] == input[:id] })[:value]] }]
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
66
  job_organization = Taxonomy.find_by(id: job.task.input[:current_organization_id])
67
67
  job_location = Taxonomy.find_by(id: job.task.input[:current_location_id])
68
68
  render :json => {
@@ -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
 
@@ -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
@@ -62,14 +62,16 @@ class JobInvocation < ApplicationRecord
62
62
 
63
63
  has_many :targeted_hosts, :through => :targeting, :source => :hosts
64
64
  scoped_search :on => 'targeted_host_id', :rename => 'targeted_host_id', :operators => ['= '],
65
- :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host
65
+ :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host,
66
+ :validator => ScopedSearch::Validators::INTEGER
66
67
 
67
68
  scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
68
69
  :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
69
70
 
70
71
  scoped_search :relation => :recurring_logic, :on => 'purpose', :rename => 'recurring_logic.purpose'
71
72
 
72
- scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
73
+ scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id',
74
+ :validator => ScopedSearch::Validators::INTEGER
73
75
 
74
76
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
75
77
  :ext_method => :search_by_recurring_logic, :only_explicit => true,
@@ -211,13 +213,7 @@ class JobInvocation < ApplicationRecord
211
213
 
212
214
  def pattern_template_invocation_for_host(host)
213
215
  providers = available_providers(host)
214
- providers.each do |provider|
215
- pattern_template_invocations.each do |template_invocation|
216
- if template_invocation.template.provider_type == provider
217
- return template_invocation
218
- end
219
- end
220
- end
216
+ pattern_template_invocations.find { |pti| providers.include? pti.template.provider_type }
221
217
  end
222
218
 
223
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)
@@ -9,7 +9,7 @@ class RemoteExecutionProvider
9
9
 
10
10
  def registered_name
11
11
  klass = self
12
- providers.key(klass)
12
+ ::RemoteExecutionProvider.providers.key(klass)
13
13
  end
14
14
 
15
15
  def proxy_feature
@@ -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' %>
@@ -108,7 +108,7 @@ handle_zypp_res_codes () {
108
108
  [ -x "$(command -v subscription-manager)" ] && subscription-manager refresh
109
109
  export DEBIAN_FRONTEND=noninteractive
110
110
  apt-get -y update
111
- apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= input("options") %> <%= 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") %>
112
112
  <% elsif package_manager == 'zypper' -%>
113
113
  <%-
114
114
  if action == "group install"
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
@@ -48,9 +51,6 @@ Rails.application.routes.draw do
48
51
  get 'ui_job_wizard/resources', to: 'ui_job_wizard#resources'
49
52
  get 'ui_job_wizard/job_invocation', to: 'ui_job_wizard#job_invocation'
50
53
 
51
- match '/experimental/job_wizard/new', to: 'react#index', :via => [:get]
52
- match '/experimental/job_wizard/:id/rerun', to: 'react#index', :via => [:get]
53
-
54
54
  namespace :api, :defaults => {:format => 'json'} do
55
55
  scope '(:apiv)', :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
56
56
  resources :job_invocations, :except => [:new, :edit, :update, :destroy] 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