foreman_remote_execution 4.5.6 → 4.6.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 -1
- data/app/controllers/ui_job_wizard_controller.rb +0 -7
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +1 -5
- data/app/helpers/remote_execution_helper.rb +3 -9
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -5
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +0 -2
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +70 -0
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +0 -6
- data/app/models/host_status/execution_status.rb +3 -3
- data/app/models/job_invocation.rb +6 -9
- data/app/models/job_invocation_composer.rb +4 -4
- data/app/models/remote_execution_feature.rb +1 -5
- data/app/models/remote_execution_provider.rb +2 -3
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/targeting.rb +1 -5
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/templates/ssh/module_action.erb +0 -1
- data/app/views/templates/ssh/power_action.erb +0 -2
- data/app/views/templates/ssh/puppet_run_once.erb +0 -1
- data/foreman_remote_execution.gemspec +0 -1
- data/lib/foreman_remote_execution/engine.rb +2 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/models/orchestration/ssh_test.rb +56 -0
- data/test/unit/job_invocation_composer_test.rb +1 -14
- data/test/unit/job_invocation_test.rb +1 -1
- data/test/unit/remote_execution_provider_test.rb +0 -12
- data/webpack/JobWizard/JobWizard.js +16 -59
- data/webpack/JobWizard/JobWizard.scss +0 -58
- data/webpack/JobWizard/JobWizardConstants.js +0 -18
- data/webpack/JobWizard/JobWizardSelectors.js +0 -9
- data/webpack/JobWizard/__tests__/JobWizard.test.js +13 -0
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +32 -0
- data/webpack/JobWizard/__tests__/fixtures.js +2 -112
- data/webpack/JobWizard/__tests__/integration.test.js +92 -16
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -37
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +55 -116
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +16 -150
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +249 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +2 -4
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +51 -123
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +113 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +0 -1
- data/webpack/JobWizard/steps/form/SelectField.js +2 -14
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +37 -0
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +23 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +1 -18
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +0 -1
- metadata +13 -35
- data/app/models/host_proxy_invocation.rb +0 -4
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +0 -12
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +0 -67
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +0 -23
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +0 -25
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +0 -23
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +0 -37
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +0 -50
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +0 -66
- data/webpack/JobWizard/steps/Schedule/QueryType.js +0 -48
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +0 -61
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +0 -25
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +0 -51
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +0 -22
- data/webpack/JobWizard/steps/Schedule/index.js +0 -44
- data/webpack/JobWizard/steps/form/Formatter.js +0 -150
- data/webpack/JobWizard/steps/form/NumberInput.js +0 -35
- data/webpack/JobWizard/steps/form/WizardTitle.js +0 -14
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14dc89216ec220750d38149b86e0ecded26e13d99678505302a5a157b0c7c9e2
|
4
|
+
data.tar.gz: b2091af78be3ed0fec0d5acea056a49e601e6d1ef96954c49976fd1c0da39ecb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10d901fec16519db44d05d9b9ecb2b15923e1a2e439f1e0032778b9272ed4c48a446436b4ef7375bb5113f8067d5596035b5a7fa593a522fc37e56a472d51c31
|
7
|
+
data.tar.gz: cb7686bf72b55bf65bbd35cee82910925f12b9dd29ec573dc96bb340fb1cc629833a80239e96317dcef2163d17d00ba93db92f5f50a10c5a4f4363d77647f729
|
@@ -67,7 +67,7 @@ class JobInvocationsController < ApplicationController
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def index
|
70
|
-
@job_invocations = resource_base_search_and_page.
|
70
|
+
@job_invocations = resource_base_search_and_page.with_task.order('job_invocations.id DESC')
|
71
71
|
end
|
72
72
|
|
73
73
|
# refreshes the form
|
@@ -10,12 +10,9 @@ class UiJobWizardController < ::Api::V2::BaseController
|
|
10
10
|
|
11
11
|
def template
|
12
12
|
job_template = JobTemplate.authorized.find(params[:id])
|
13
|
-
advanced_template_inputs, template_inputs = map_template_inputs(job_template.template_inputs_with_foreign).partition { |x| x["advanced"] }
|
14
13
|
render :json => {
|
15
14
|
:job_template => job_template,
|
16
15
|
:effective_user => job_template.effective_user,
|
17
|
-
:template_inputs => template_inputs,
|
18
|
-
:advanced_template_inputs => advanced_template_inputs,
|
19
16
|
}
|
20
17
|
end
|
21
18
|
|
@@ -23,10 +20,6 @@ class UiJobWizardController < ::Api::V2::BaseController
|
|
23
20
|
nested_resource || 'job_template'
|
24
21
|
end
|
25
22
|
|
26
|
-
def map_template_inputs(template_inputs_with_foreign)
|
27
|
-
template_inputs_with_foreign.map { |input| input.attributes.merge({:resource_type => input.resource_type&.tableize }) }
|
28
|
-
end
|
29
|
-
|
30
23
|
def resource_class
|
31
24
|
JobTemplate
|
32
25
|
end
|
@@ -7,9 +7,7 @@ module ForemanRemoteExecution
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def multiple_actions
|
10
|
-
|
11
|
-
res += [ [_('Schedule Remote Job'), new_job_invocation_path, false] ] if authorized_for(controller: :job_invocations, action: :new)
|
12
|
-
res
|
10
|
+
super + [ [_('Schedule Remote Job'), new_job_invocation_path, false] ]
|
13
11
|
end
|
14
12
|
|
15
13
|
def schedule_job_multi_button(*args)
|
@@ -23,14 +21,12 @@ module ForemanRemoteExecution
|
|
23
21
|
end
|
24
22
|
|
25
23
|
def rex_host_features(*args)
|
26
|
-
return unless authorized_for(controller: :job_invocations, action: :create)
|
27
24
|
RemoteExecutionFeature.with_host_action_button.order(:label).map do |feature|
|
28
25
|
link_to(_('%s') % feature.name, job_invocations_path(:host_ids => [args.first.id], :feature => feature.label), :method => :post)
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
29
|
def schedule_job_button(*args)
|
33
|
-
return unless authorized_for(controller: :job_invocations, action: :new)
|
34
30
|
link_to(_('Schedule Remote Job'), new_job_invocation_path(:host_ids => [args.first.id]), :id => :run_button, :class => 'btn btn-default')
|
35
31
|
end
|
36
32
|
|
@@ -7,10 +7,6 @@ module RemoteExecutionHelper
|
|
7
7
|
@job_hosts_authorizer ||= Authorizer.new(User.current, :collection => @hosts)
|
8
8
|
end
|
9
9
|
|
10
|
-
def host_tasks_authorizer
|
11
|
-
@host_tasks_authorizer ||= Authorizer.new(User.current, :collection => @job_invocation.sub_tasks)
|
12
|
-
end
|
13
|
-
|
14
10
|
def host_counter(label, count)
|
15
11
|
content_tag(:div, :class => 'host_counter') do
|
16
12
|
content_tag(:div, label, :class => 'header') + content_tag(:div, count.to_s, :class => 'count')
|
@@ -40,7 +36,7 @@ module RemoteExecutionHelper
|
|
40
36
|
'data-method': 'get', id: "#{host.name}-actions-rerun" } }
|
41
37
|
end
|
42
38
|
|
43
|
-
if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks
|
39
|
+
if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
|
44
40
|
links << { title: _('Host task'),
|
45
41
|
action: { href: foreman_tasks_task_path(host_task),
|
46
42
|
'data-method': 'get', id: "#{host.name}-actions-task" } }
|
@@ -113,14 +109,12 @@ module RemoteExecutionHelper
|
|
113
109
|
:class => 'btn btn-danger',
|
114
110
|
:title => _('Try to cancel the job on a host'),
|
115
111
|
:disabled => !task.cancellable?,
|
116
|
-
:method => :post
|
117
|
-
:remote => true)
|
112
|
+
:method => :post)
|
118
113
|
buttons << link_to(_('Abort Job'), abort_foreman_tasks_task_path(task),
|
119
114
|
:class => 'btn btn-danger',
|
120
115
|
:title => _('Try to abort the job on a host without waiting for its result'),
|
121
116
|
:disabled => !task.cancellable?,
|
122
|
-
:method => :post
|
123
|
-
:remote => true)
|
117
|
+
:method => :post)
|
124
118
|
end
|
125
119
|
buttons
|
126
120
|
end
|
@@ -47,16 +47,12 @@ module Actions
|
|
47
47
|
script = renderer.render
|
48
48
|
raise _('Failed rendering template: %s') % renderer.error_message unless script
|
49
49
|
|
50
|
-
first_execution = host.executed_through_proxies.where(:id => proxy.id).none?
|
51
|
-
host.executed_through_proxies << proxy if first_execution
|
52
|
-
|
53
50
|
additional_options = { :hostname => provider.find_ip_or_hostname(host),
|
54
51
|
:script => script,
|
55
52
|
:execution_timeout_interval => job_invocation.execution_timeout_interval,
|
56
53
|
:secrets => secrets(host, job_invocation, provider),
|
57
54
|
:use_batch_triggering => true,
|
58
|
-
:use_concurrency_control => options[:use_concurrency_control]
|
59
|
-
:first_execution => first_execution }
|
55
|
+
:use_concurrency_control => options[:use_concurrency_control]}
|
60
56
|
action_options = provider.proxy_command_options(template_invocation, host)
|
61
57
|
.merge(additional_options)
|
62
58
|
|
@@ -72,7 +72,7 @@ module Actions
|
|
72
72
|
|
73
73
|
def total_count
|
74
74
|
# For compatibility with already existing tasks
|
75
|
-
return output[:total_count]
|
75
|
+
return output[:total_count] unless output.has_key?(:host_count) || task.pending?
|
76
76
|
|
77
77
|
output[:host_count] || hosts.count
|
78
78
|
end
|
@@ -6,8 +6,6 @@ module ForemanRemoteExecution
|
|
6
6
|
has_many :template_invocations, :dependent => :destroy, :foreign_key => 'host_id'
|
7
7
|
has_one :execution_status_object, :class_name => 'HostStatus::ExecutionStatus', :foreign_key => 'host_id', :dependent => :destroy
|
8
8
|
has_many :run_host_job_tasks, :through => :template_invocations
|
9
|
-
has_many :host_proxy_invocations, :foreign_key => 'host_id', :dependent => :destroy
|
10
|
-
has_many :executed_through_proxies, :through => :host_proxy_invocations, :source => 'smart_proxy'
|
11
9
|
|
12
10
|
scoped_search :relation => :run_host_job_tasks, :on => :result, :rename => 'job_invocation.result',
|
13
11
|
:ext_method => :search_by_job_invocation,
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ForemanRemoteExecution
|
2
|
+
module Orchestration::SSH
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_destroy :ssh_destroy
|
7
|
+
after_validation :queue_ssh_destroy
|
8
|
+
register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
|
9
|
+
end
|
10
|
+
|
11
|
+
def drop_from_known_hosts(proxy_id)
|
12
|
+
_, _, target = host_kind_target
|
13
|
+
return true if target.nil?
|
14
|
+
|
15
|
+
proxy = ::SmartProxy.find(proxy_id)
|
16
|
+
begin
|
17
|
+
proxy.drop_host_from_known_hosts(target)
|
18
|
+
rescue ::ProxyAPI::ProxyException => e
|
19
|
+
if e.wrapped_exception.is_a?(RestClient::NotFound)
|
20
|
+
# ignore 404 when known_hosts entry is missing or the module was not enabled
|
21
|
+
Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
|
22
|
+
else
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
rescue => e
|
26
|
+
Rails.logger.warn e.message
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def ssh_destroy
|
33
|
+
logger.debug "Scheduling SSH known_hosts cleanup"
|
34
|
+
|
35
|
+
host, _kind, _target = host_kind_target
|
36
|
+
# #remote_execution_proxies may not be defined on the host object in some case
|
37
|
+
# for example Host::Discovered does not have it defined, even though these hosts
|
38
|
+
# have Nic::Managed interfaces associated with them
|
39
|
+
proxies = (host.try(:remote_execution_proxies, 'SSH') || {}).values
|
40
|
+
proxies.flatten.uniq.each do |proxy|
|
41
|
+
queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
|
42
|
+
priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def queue_ssh_destroy
|
47
|
+
should_drop_from_known_hosts? && ssh_destroy
|
48
|
+
end
|
49
|
+
|
50
|
+
def should_drop_from_known_hosts?
|
51
|
+
host, = host_kind_target
|
52
|
+
host && !host.new_record? && host.build && host.changes.key?('build')
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def host_kind_target
|
58
|
+
if self.is_a?(::Host::Base)
|
59
|
+
[self, 'host', name]
|
60
|
+
else
|
61
|
+
[self.host, 'interface', ip]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def queue_id(proxy_id)
|
66
|
+
_, kind, id = host_kind_target
|
67
|
+
"ssh_remove_known_hosts_#{kind}_#{id}_#{proxy_id}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -64,11 +64,11 @@ class HostStatus::ExecutionStatus < HostStatus::Status
|
|
64
64
|
|
65
65
|
case status
|
66
66
|
when OK
|
67
|
-
[ "foreman_tasks_tasks.state = 'stopped' AND
|
67
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND result = 'success'" ]
|
68
68
|
when CANCELLED
|
69
|
-
[ "foreman_tasks_tasks.state = 'stopped' AND
|
69
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND result = 'cancelled'" ]
|
70
70
|
when ERROR
|
71
|
-
[ "foreman_tasks_tasks.state = 'stopped' AND (
|
71
|
+
[ "foreman_tasks_tasks.state = 'stopped' AND (result = 'error' OR result = 'warning')" ]
|
72
72
|
when QUEUED
|
73
73
|
[ "foreman_tasks_tasks.state = 'scheduled' OR foreman_tasks_tasks.state IS NULL" ]
|
74
74
|
when RUNNING
|
@@ -65,6 +65,8 @@ class JobInvocation < ApplicationRecord
|
|
65
65
|
scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
|
66
66
|
:complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
|
67
67
|
|
68
|
+
scope :with_task, -> { references(:task) }
|
69
|
+
|
68
70
|
scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
|
69
71
|
|
70
72
|
scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
|
@@ -97,7 +99,7 @@ class JobInvocation < ApplicationRecord
|
|
97
99
|
def self.search_by_status(key, operator, value)
|
98
100
|
conditions = HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.sql_conditions_for(value)
|
99
101
|
conditions[0] = "NOT (#{conditions[0]})" if operator == '<>'
|
100
|
-
{ :conditions => sanitize_sql_for_conditions(conditions), :
|
102
|
+
{ :conditions => sanitize_sql_for_conditions(conditions), :include => :task }
|
101
103
|
end
|
102
104
|
|
103
105
|
def self.search_by_recurring_logic(key, operator, value)
|
@@ -191,16 +193,11 @@ class JobInvocation < ApplicationRecord
|
|
191
193
|
end
|
192
194
|
|
193
195
|
def total_hosts_count
|
194
|
-
count = _('N/A')
|
195
|
-
|
196
196
|
if targeting.resolved?
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
targeting.hosts.count
|
201
|
-
end
|
197
|
+
task&.main_action&.total_count || targeting.hosts.count
|
198
|
+
else
|
199
|
+
_('N/A')
|
202
200
|
end
|
203
|
-
count
|
204
201
|
end
|
205
202
|
|
206
203
|
def pattern_template_invocation_for_host(host)
|
@@ -209,7 +209,7 @@ class JobInvocationComposer
|
|
209
209
|
def format_datetime(datetime)
|
210
210
|
return datetime if datetime.blank?
|
211
211
|
|
212
|
-
Time.parse(datetime).
|
212
|
+
Time.parse(datetime).strftime('%Y-%m-%d %H:%M')
|
213
213
|
end
|
214
214
|
end
|
215
215
|
|
@@ -296,7 +296,7 @@ class JobInvocationComposer
|
|
296
296
|
attr_reader :feature_label, :feature, :provided_inputs
|
297
297
|
|
298
298
|
def initialize(feature_label, hosts, provided_inputs = {})
|
299
|
-
@feature = RemoteExecutionFeature.feature
|
299
|
+
@feature = RemoteExecutionFeature.feature(feature_label)
|
300
300
|
@provided_inputs = provided_inputs
|
301
301
|
translator = HostIdsTranslator.new(hosts)
|
302
302
|
@host_bookmark = translator.bookmark
|
@@ -405,7 +405,7 @@ class JobInvocationComposer
|
|
405
405
|
job_invocation.effective_user_password = params[:effective_user_password]
|
406
406
|
|
407
407
|
if @reruns && job_invocation.targeting.static?
|
408
|
-
job_invocation.targeting.
|
408
|
+
job_invocation.targeting.host_ids = JobInvocation.find(@reruns).targeting.host_ids
|
409
409
|
job_invocation.targeting.mark_resolved!
|
410
410
|
end
|
411
411
|
|
@@ -636,7 +636,7 @@ class JobInvocationComposer
|
|
636
636
|
setting_value = Setting['remote_execution_form_job_template']
|
637
637
|
return default_value unless setting_value
|
638
638
|
|
639
|
-
form_template = JobTemplate.
|
639
|
+
form_template = JobTemplate.find_by :name => setting_value
|
640
640
|
return default_value unless form_template
|
641
641
|
|
642
642
|
if block_given?
|
@@ -20,11 +20,7 @@ class RemoteExecutionFeature < ApplicationRecord
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.feature(label)
|
23
|
-
self.find_by(label: label)
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.feature!(label)
|
27
|
-
feature(label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
|
23
|
+
self.find_by(label: label) || raise(::Foreman::Exception.new(N_('Unknown remote execution feature %s'), label))
|
28
24
|
end
|
29
25
|
|
30
26
|
def self.register(label, name, options = {})
|
@@ -89,8 +89,7 @@ class RemoteExecutionProvider
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def host_setting(host, setting)
|
92
|
-
|
93
|
-
param_value.nil? ? Setting[setting] : param_value
|
92
|
+
host.host_param(setting.to_s) || Setting[setting]
|
94
93
|
end
|
95
94
|
|
96
95
|
def ssh_password(_host)
|
@@ -112,7 +111,7 @@ class RemoteExecutionProvider
|
|
112
111
|
end
|
113
112
|
|
114
113
|
def proxy_action_class
|
115
|
-
'
|
114
|
+
'Proxy::RemoteExecution::Ssh::Actions::RunScript'
|
116
115
|
end
|
117
116
|
|
118
117
|
# Return a specific proxy selector to use for running a given template
|
@@ -70,13 +70,13 @@ class Setting::RemoteExecution < Setting
|
|
70
70
|
self.set('remote_execution_form_job_template',
|
71
71
|
N_('Choose a job template that is pre-selected in job invocation form'),
|
72
72
|
'Run Command - SSH Default',
|
73
|
-
|
73
|
+
_('Form Job Template'),
|
74
74
|
nil,
|
75
75
|
{ :collection => proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] } }),
|
76
76
|
self.set('remote_execution_job_invocation_report_template',
|
77
77
|
N_('Select a report template used for generating a report for a particular remote execution job'),
|
78
78
|
'Jobs - Invocation report template',
|
79
|
-
|
79
|
+
_('Job Invocation Report Template'),
|
80
80
|
nil,
|
81
81
|
{ :collection => proc { self.job_invocation_report_templates_select } }),
|
82
82
|
]
|
data/app/models/targeting.rb
CHANGED
@@ -46,13 +46,9 @@ class Targeting < ApplicationRecord
|
|
46
46
|
# pluck(:id) returns duplicate results for HostCollections
|
47
47
|
host_ids = User.as(user.login) { Host.authorized(RESOLVE_PERMISSION, Host).search_for(search_query).order(:name, :id).pluck(:id).uniq }
|
48
48
|
host_ids.shuffle!(random: Random.new) if randomized_ordering
|
49
|
-
self.assign_host_ids(host_ids)
|
50
|
-
self.save(:validate => false)
|
51
|
-
end
|
52
|
-
|
53
|
-
def assign_host_ids(host_ids)
|
54
49
|
# this can be optimized even more, by introducing bulk insert
|
55
50
|
self.targeting_hosts.build(host_ids.map { |id| { :host_id => id } })
|
51
|
+
self.save(:validate => false)
|
56
52
|
end
|
57
53
|
|
58
54
|
def dynamic?
|
@@ -21,7 +21,7 @@
|
|
21
21
|
<% @job_invocations.each do |invocation| %>
|
22
22
|
<tr>
|
23
23
|
<td class="text_warp"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
|
24
|
-
<td><%= trunc_with_tooltip(invocation
|
24
|
+
<td><%= trunc_with_tooltip(invocation&.targeting&.search_query, 15) %></td>
|
25
25
|
<td><%= link_to_invocation_task_if_authorized(invocation) %></td>
|
26
26
|
<td><%= invocation_result(invocation, :success_count) %></td>
|
27
27
|
<td><%= invocation_result(invocation, :failed_count) %></td>
|
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
|
|
25
25
|
|
26
26
|
s.add_dependency 'deface'
|
27
27
|
s.add_dependency 'dynflow', '>= 1.0.2', '< 2.0.0'
|
28
|
-
s.add_dependency 'foreman_remote_execution_core'
|
29
28
|
s.add_dependency 'foreman-tasks', '>= 4.1.0'
|
30
29
|
|
31
30
|
s.add_development_dependency 'factory_bot_rails', '~> 4.8.0'
|
@@ -201,10 +201,12 @@ module ForemanRemoteExecution
|
|
201
201
|
|
202
202
|
Host::Managed.prepend ForemanRemoteExecution::HostExtensions
|
203
203
|
Host::Managed.include ForemanTasks::Concerns::HostActionSubject
|
204
|
+
Host::Managed.include ForemanRemoteExecution::Orchestration::SSH
|
204
205
|
|
205
206
|
(Nic::Base.descendants + [Nic::Base]).each do |klass|
|
206
207
|
klass.send(:include, ForemanRemoteExecution::NicExtensions)
|
207
208
|
end
|
209
|
+
Nic::Managed.include ForemanRemoteExecution::Orchestration::SSH
|
208
210
|
|
209
211
|
Bookmark.include ForemanRemoteExecution::BookmarkExtensions
|
210
212
|
HostsHelper.prepend ForemanRemoteExecution::HostsHelperExtensions
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class SSHOrchestrationTest < ActiveSupport::TestCase
|
4
|
+
let(:host) { FactoryBot.create(:host, :managed, :with_subnet) }
|
5
|
+
let(:proxy) { FactoryBot.create(:smart_proxy, :ssh) }
|
6
|
+
let(:interface) { host.interfaces.first }
|
7
|
+
|
8
|
+
before { interface.subnet.remote_execution_proxies = [proxy] }
|
9
|
+
|
10
|
+
it 'attempts to drop IP address and hostname from smart proxies on destroy' do
|
11
|
+
host.stubs(:skip_orchestration?).returns false
|
12
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
|
13
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
|
14
|
+
host.destroy
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'attempts to drop IP address and hostname from smart proxies on rebuild' do
|
18
|
+
host.stubs(:skip_orchestration?).returns false
|
19
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
|
20
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
|
21
|
+
|
22
|
+
host.build = true
|
23
|
+
host.save!
|
24
|
+
|
25
|
+
ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
|
26
|
+
"ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
|
27
|
+
_(host.queue.task_ids).must_equal ids
|
28
|
+
_(host.queue.items.map(&:status)).must_equal %w(completed completed)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not fail on 404 from the smart proxy' do
|
32
|
+
host.stubs(:skip_orchestration?).returns false
|
33
|
+
::ProxyAPI::RemoteExecutionSSH.any_instance.expects(:delete).raises(RestClient::ResourceNotFound).twice
|
34
|
+
host.build = true
|
35
|
+
host.save!
|
36
|
+
ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
|
37
|
+
"ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
|
38
|
+
_(host.queue.task_ids).must_equal ids
|
39
|
+
_(host.queue.items.map(&:status)).must_equal %w(completed completed)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not trigger the removal when creating a new host' do
|
43
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).never
|
44
|
+
host = Host::Managed.new(:name => 'test', :ip => '127.0.0.1')
|
45
|
+
host.stubs(:skip_orchestration?).returns false
|
46
|
+
_(host.queue.task_ids).must_equal []
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'does not call to the proxy when target is nil' do
|
50
|
+
host.stubs(:skip_orchestration?).returns false
|
51
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
|
52
|
+
host.interfaces.first.stubs(:ip)
|
53
|
+
host.destroy
|
54
|
+
_(host.queue.items.map(&:status)).must_equal %w(completed completed)
|
55
|
+
end
|
56
|
+
end
|
@@ -864,9 +864,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
864
864
|
it 'marks targeting as resolved if static' do
|
865
865
|
created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
|
866
866
|
assert created.targeting.resolved?
|
867
|
-
created.targeting.
|
868
|
-
created.targeting.reload
|
869
|
-
assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
|
867
|
+
assert_equal job_invocation.template_invocations_host_ids, created.targeting.host_ids
|
870
868
|
end
|
871
869
|
|
872
870
|
it 'takes randomized_ordering from the original job invocation when rerunning failed' do
|
@@ -876,17 +874,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
876
874
|
composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
|
877
875
|
assert composer.job_invocation.targeting.randomized_ordering
|
878
876
|
end
|
879
|
-
|
880
|
-
it 'works with invalid hosts' do
|
881
|
-
host = job_invocation.targeting.hosts.first
|
882
|
-
::Host::Managed.any_instance.stubs(:valid?).returns(false)
|
883
|
-
composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
|
884
|
-
targeting = composer.compose.job_invocation.targeting
|
885
|
-
targeting.save!
|
886
|
-
targeting.reload
|
887
|
-
assert targeting.valid?
|
888
|
-
assert_equal targeting.hosts.pluck(:id), [host.id]
|
889
|
-
end
|
890
877
|
end
|
891
878
|
|
892
879
|
describe '.for_feature' do
|
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'is able to perform search through job invocations' do
|
13
|
-
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
|
13
|
+
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).with_task.order('job_invocations.id DESC')
|
14
14
|
_(found_jobs).must_equal [job_invocation]
|
15
15
|
end
|
16
16
|
|
@@ -50,18 +50,6 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
describe '.host_setting' do
|
54
|
-
let(:host) { FactoryBot.create(:host) }
|
55
|
-
|
56
|
-
it 'honors falsey values set as a host parameter' do
|
57
|
-
key = 'remote_execution_connect_by_ip'
|
58
|
-
Setting[key] = true
|
59
|
-
host.parameters << HostParameter.new(name: key, value: false)
|
60
|
-
|
61
|
-
refute RemoteExecutionProvider.host_setting(host, key)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
53
|
describe SSHExecutionProvider do
|
66
54
|
before { User.current = FactoryBot.build(:user, :admin) }
|
67
55
|
after { User.current = nil }
|