foreman_remote_execution 8.0.0 → 8.1.0

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 (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