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.
- checksums.yaml +4 -4
- data/app/controllers/job_invocations_controller.rb +1 -2
- data/app/controllers/job_templates_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +1 -1
- data/app/helpers/job_invocations_helper.rb +0 -7
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/app/lib/actions/remote_execution/proxy_action.rb +46 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +38 -11
- data/app/lib/actions/remote_execution/run_hosts_job.rb +7 -6
- data/app/lib/actions/remote_execution/template_invocation_progress_logging.rb +27 -0
- data/app/models/job_invocation.rb +5 -9
- data/app/models/job_invocation_composer.rb +4 -0
- data/app/models/remote_execution_provider.rb +10 -2
- data/app/models/ssh_execution_provider.rb +1 -0
- data/app/models/template_invocation.rb +1 -0
- data/app/models/template_invocation_event.rb +11 -0
- data/app/views/job_invocations/_form.html.erb +4 -0
- data/app/views/job_invocations/new.html.erb +5 -0
- data/app/views/templates/script/package_action.erb +1 -1
- data/config/routes.rb +5 -5
- data/db/migrate/20220713095705_create_template_invocation_events.rb +17 -0
- data/db/migrate/20220822155946_add_time_to_pickup_to_job_invocation.rb +5 -0
- data/extra/cockpit/foreman-cockpit-session +303 -230
- data/extra/cockpit/foreman-cockpit.service +1 -0
- data/foreman_remote_execution.gemspec +1 -1
- data/lib/foreman_remote_execution/engine.rb +12 -7
- data/lib/foreman_remote_execution/tasks/explain_proxy_selection.rake +131 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/unit/remote_execution_provider_test.rb +22 -0
- data/webpack/JobWizard/JobWizard.js +53 -18
- data/webpack/JobWizard/JobWizard.scss +3 -0
- data/webpack/JobWizard/JobWizardConstants.js +1 -1
- data/webpack/JobWizard/JobWizardHelpers.js +15 -0
- data/webpack/JobWizard/JobWizardPageRerun.js +29 -5
- data/webpack/JobWizard/JobWizardSelectors.js +8 -2
- data/webpack/JobWizard/__tests__/JobWizardPageRerun.test.js +5 -0
- data/webpack/JobWizard/__tests__/fixtures.js +26 -2
- data/webpack/JobWizard/autofill.js +32 -10
- data/webpack/JobWizard/index.js +25 -6
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +25 -0
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +12 -1
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +41 -6
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +1 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +1 -1
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +6 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +28 -20
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +32 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +2 -2
- data/webpack/JobWizard/steps/ReviewDetails/index.js +1 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +21 -1
- data/webpack/JobWizard/steps/form/Formatter.js +22 -6
- data/webpack/JobWizard/steps/form/ResourceSelect.js +97 -10
- data/webpack/JobWizard/steps/form/SearchSelect.js +2 -2
- data/webpack/JobWizard/steps/form/SelectField.js +4 -0
- data/webpack/JobWizard/submit.js +3 -1
- data/webpack/JobWizard/validation.js +1 -0
- data/webpack/Routes/routes.js +3 -3
- data/webpack/react_app/components/FeaturesDropdown/actions.js +23 -2
- data/webpack/react_app/components/FeaturesDropdown/index.js +2 -0
- data/webpack/react_app/components/HostKebab/KebabItems.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +5 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +51 -59
- data/webpack/react_app/extend/Fills.js +3 -3
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a4cbf1cdf74f0bd20c4729a8bc05a7a3bb2a958bace18d30755fb600d0a999c
|
4
|
+
data.tar.gz: 7eac3c732b1c9b05e9c0909d952b859010cdeef677324353901859232d8ab3b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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 =>
|
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
|
-
|
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
|
-
|
72
|
-
|
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(
|
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
|
-
|
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
|
-
{
|
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
|
@@ -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: '
|
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
|