foreman_remote_execution 3.2.1 → 3.2.2
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/api/v2/job_invocations_controller.rb +5 -1
- data/app/controllers/api/v2/template_invocations_controller.rb +4 -1
- data/app/helpers/job_invocations_helper.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +10 -5
- data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +42 -14
- data/test/models/orchestration/ssh_test.rb +32 -0
- data/test/unit/concerns/host_extensions_test.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19c80592b90b1ba20ac213bae2ff36e9608202161e29718a063d3ac52ed249b3
|
4
|
+
data.tar.gz: daa58fc0cfe1a9b788a49372c1d200579502331ef9e1f1424fa4a81270898a8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3b9ee1922a5ece712f8f431eb84de7ab5793d5108fe4959ce690a0acd6edb14f21ab5135cc1b7bcf37d3fbe81af45731fe1900e1803683c131bc3dba13aa931
|
7
|
+
data.tar.gz: 5db356770b3017319ef2e4f0d1a16598025c9227b540661b2f3e6104f2bd522dbbfb351266eae0bc63d19667a2edc079de0136b50e648e6abddf973bbbe58ead
|
@@ -19,6 +19,10 @@ module Api
|
|
19
19
|
api :GET, '/job_invocations/:id', N_('Show job invocation')
|
20
20
|
param :id, :identifier, :required => true
|
21
21
|
def show
|
22
|
+
@hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
|
23
|
+
@template_invocations = @job_invocation.template_invocations
|
24
|
+
.where(host: @hosts)
|
25
|
+
.includes(:input_values)
|
22
26
|
end
|
23
27
|
|
24
28
|
def_param_group :job_invocation do
|
@@ -146,7 +150,7 @@ module Api
|
|
146
150
|
end
|
147
151
|
|
148
152
|
def find_host
|
149
|
-
@host =
|
153
|
+
@host = @nested_obj.targeting.hosts.authorized(:view_hosts, Host).find(params['host_id'])
|
150
154
|
rescue ActiveRecord::RecordNotFound
|
151
155
|
not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
|
152
156
|
end
|
@@ -26,7 +26,10 @@ module Api
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def resource_scope_for_template_invocations
|
29
|
-
@job_invocation.template_invocations
|
29
|
+
@job_invocation.template_invocations
|
30
|
+
.includes(:host)
|
31
|
+
.where(host: Host.authorized(:view_hosts, Host))
|
32
|
+
.search_for(*search_options)
|
30
33
|
end
|
31
34
|
|
32
35
|
def find_job_invocation
|
@@ -29,7 +29,7 @@ module JobInvocationsHelper
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def preview_hosts(template_invocation)
|
32
|
-
hosts = template_invocation.targeting.hosts.take(20)
|
32
|
+
hosts = template_invocation.targeting.hosts.authorized(:view_hosts, Host).take(20)
|
33
33
|
hosts.map do |host|
|
34
34
|
collapsed_preview(host) +
|
35
35
|
render(:partial => 'job_invocations/user_input',
|
@@ -49,7 +49,7 @@ module ForemanRemoteExecution
|
|
49
49
|
keys = remote_execution_ssh_keys
|
50
50
|
source = 'global'
|
51
51
|
if keys.present?
|
52
|
-
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| v
|
52
|
+
value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
|
53
53
|
params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
|
54
54
|
end
|
55
55
|
[:remote_execution_ssh_user, :remote_execution_effective_user_method,
|
@@ -8,11 +8,16 @@ module ForemanRemoteExecution
|
|
8
8
|
register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
|
9
9
|
end
|
10
10
|
|
11
|
-
def drop_from_known_hosts(
|
12
|
-
|
11
|
+
def drop_from_known_hosts(proxy_id)
|
12
|
+
_, _, target = host_kind_target
|
13
|
+
return true if target.nil?
|
14
|
+
|
13
15
|
proxy = ::SmartProxy.find(proxy_id)
|
14
16
|
begin
|
15
17
|
proxy.drop_host_from_known_hosts(target)
|
18
|
+
rescue RestClient::ResourceNotFound => e
|
19
|
+
# ignore 404 when known_hosts entry is missing or the module was not enabled
|
20
|
+
Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
|
16
21
|
rescue => e
|
17
22
|
Rails.logger.warn e.message
|
18
23
|
return false
|
@@ -23,11 +28,11 @@ module ForemanRemoteExecution
|
|
23
28
|
def ssh_destroy
|
24
29
|
logger.debug "Scheduling SSH known_hosts cleanup"
|
25
30
|
|
26
|
-
host, _kind,
|
31
|
+
host, _kind, _target = host_kind_target
|
27
32
|
proxies = host.remote_execution_proxies('SSH').values
|
28
33
|
proxies.flatten.uniq.each do |proxy|
|
29
34
|
queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
|
30
|
-
priority: 200, action: [self, :drop_from_known_hosts,
|
35
|
+
priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
@@ -37,7 +42,7 @@ module ForemanRemoteExecution
|
|
37
42
|
|
38
43
|
def should_drop_from_known_hosts?
|
39
44
|
host, = host_kind_target
|
40
|
-
host
|
45
|
+
host && !host.new_record? && host.build && host.changes.key?('build')
|
41
46
|
end
|
42
47
|
|
43
48
|
private
|
@@ -19,7 +19,7 @@ child :targeting do
|
|
19
19
|
attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label,
|
20
20
|
:randomized_ordering
|
21
21
|
|
22
|
-
child
|
22
|
+
child @hosts do
|
23
23
|
extends 'api/v2/hosts/base'
|
24
24
|
end
|
25
25
|
end
|
@@ -28,7 +28,7 @@ child :task do
|
|
28
28
|
attributes :id, :state
|
29
29
|
end
|
30
30
|
|
31
|
-
child
|
31
|
+
child @template_invocations do
|
32
32
|
attributes :template_id, :template_name
|
33
33
|
child :input_values do
|
34
34
|
attributes :template_input_name, :template_input_id
|
@@ -4,7 +4,7 @@ module Api
|
|
4
4
|
module V2
|
5
5
|
class JobInvocationsControllerTest < ActionController::TestCase
|
6
6
|
setup do
|
7
|
-
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
7
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
|
8
8
|
@template = FactoryBot.create(:job_template, :with_input)
|
9
9
|
|
10
10
|
# Without this the template in template_invocations and in pattern_template_invocations
|
@@ -20,18 +20,32 @@ module Api
|
|
20
20
|
assert_response :success
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
describe 'show' do
|
24
|
+
test 'should get invocation detail' do
|
25
|
+
get :show, params: { :id => @invocation.id }
|
26
|
+
assert_response :success
|
27
|
+
template = ActiveSupport::JSON.decode(@response.body)
|
28
|
+
assert_not_empty template
|
29
|
+
assert_equal template['job_category'], @invocation.job_category
|
30
|
+
assert_not_empty template['targeting']['hosts']
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
test 'should get invocation detail when taxonomies are set' do
|
34
|
+
taxonomy_params = %w(organization location).reduce({}) { |acc, cur| acc.merge("#{cur}_id" => FactoryBot.create(cur)) }
|
35
|
+
get :show, params: taxonomy_params.merge(:id => @invocation.id)
|
36
|
+
assert_response :success
|
37
|
+
end
|
38
|
+
|
39
|
+
test 'should see only permitted hosts' do
|
40
|
+
@user = FactoryBot.create(:user, admin: false)
|
41
|
+
setup_user('view', 'job_invocations', nil, @user)
|
42
|
+
setup_user('view', 'hosts', 'name ~ nope.example.com', @user)
|
43
|
+
|
44
|
+
get :show, params: { :id => @invocation.id }, session: set_session_user(@user)
|
45
|
+
assert_response :success
|
46
|
+
response = ActiveSupport::JSON.decode(@response.body)
|
47
|
+
assert_empty response['targeting']['hosts']
|
48
|
+
end
|
35
49
|
end
|
36
50
|
|
37
51
|
context 'creation' do
|
@@ -108,7 +122,7 @@ module Api
|
|
108
122
|
end
|
109
123
|
|
110
124
|
describe '#output' do
|
111
|
-
let(:host) { @invocation.
|
125
|
+
let(:host) { @invocation.targeting.hosts.first }
|
112
126
|
|
113
127
|
test 'should provide output for delayed task' do
|
114
128
|
ForemanTasks::Task.any_instance.expects(:scheduled?).returns(true)
|
@@ -137,6 +151,12 @@ module Api
|
|
137
151
|
assert_equal result['message'], "Job invocation not found by id '#{invocation_id}'"
|
138
152
|
assert_response :missing
|
139
153
|
end
|
154
|
+
|
155
|
+
test 'should get output only for host in job invocation' do
|
156
|
+
get :output, params: { job_invocation_id: @invocation.id,
|
157
|
+
host_id: FactoryBot.create(:host).id }
|
158
|
+
assert_response :missing
|
159
|
+
end
|
140
160
|
end
|
141
161
|
|
142
162
|
describe 'raw output' do
|
@@ -148,7 +168,7 @@ module Api
|
|
148
168
|
let(:fake_task) do
|
149
169
|
OpenStruct.new :pending? => false, :main_action => OpenStruct.new(:live_output => fake_output)
|
150
170
|
end
|
151
|
-
let(:host) { @invocation.
|
171
|
+
let(:host) { @invocation.targeting.hosts.first }
|
152
172
|
|
153
173
|
test 'should provide raw output for a host' do
|
154
174
|
JobInvocation.any_instance.expects(:task).returns(OpenStruct.new(:scheduled? => false))
|
@@ -184,6 +204,12 @@ module Api
|
|
184
204
|
assert_nil result['output']
|
185
205
|
assert_response :success
|
186
206
|
end
|
207
|
+
|
208
|
+
test 'should get raw output only for host in job invocation' do
|
209
|
+
get :raw_output, params: { job_invocation_id: @invocation.id,
|
210
|
+
host_id: FactoryBot.create(:host).id }
|
211
|
+
assert_response :missing
|
212
|
+
end
|
187
213
|
end
|
188
214
|
|
189
215
|
test 'should cancel a job' do
|
@@ -232,11 +258,13 @@ module Api
|
|
232
258
|
end
|
233
259
|
|
234
260
|
test 'should not raise an exception when reruning failed has no hosts' do
|
261
|
+
@invocation.targeting.hosts.first.destroy
|
235
262
|
JobInvocation.any_instance.expects(:generate_description)
|
236
263
|
JobInvocationComposer.any_instance
|
237
264
|
.expects(:validate_job_category)
|
238
265
|
.with(@invocation.job_category)
|
239
266
|
.returns(@invocation.job_category)
|
267
|
+
|
240
268
|
post :rerun, params: { :id => @invocation.id, :failed_only => true }
|
241
269
|
assert_response :success
|
242
270
|
result = ActiveSupport::JSON.decode(@response.body)
|
@@ -15,10 +15,42 @@ class SSHOrchestrationTest < ActiveSupport::TestCase
|
|
15
15
|
end
|
16
16
|
|
17
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
|
+
|
18
22
|
host.build = true
|
19
23
|
host.save!
|
24
|
+
|
20
25
|
ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
|
21
26
|
"ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
|
22
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
|
+
SmartProxy.any_instance.expects(:drop_host_from_known_hosts).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)
|
23
55
|
end
|
24
56
|
end
|
@@ -48,6 +48,13 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
48
48
|
_(host.host_param('remote_execution_ssh_keys')).must_include sshkey
|
49
49
|
end
|
50
50
|
|
51
|
+
it 'merges ssh key as a string from host parameters and proxies' do
|
52
|
+
key = 'ssh-rsa not-even-a-key something@somewhere.com'
|
53
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => key)
|
54
|
+
_(host.host_param('remote_execution_ssh_keys')).must_include key
|
55
|
+
_(host.host_param('remote_execution_ssh_keys')).must_include sshkey
|
56
|
+
end
|
57
|
+
|
51
58
|
it 'has ssh keys in the parameters even when no user specified' do
|
52
59
|
# this is a case, when using the helper in provisioning templates
|
53
60
|
FactoryBot.create(:smart_proxy, :ssh)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.2.
|
4
|
+
version: 3.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deface
|