foreman_remote_execution 4.5.6 → 4.6.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.
- 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 }
|