foreman_remote_execution_core 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/foreman_remote_execution_core.rb +2 -1
- data/lib/foreman_remote_execution_core/actions.rb +1 -1
- data/lib/foreman_remote_execution_core/fake_script_runner.rb +5 -1
- data/lib/foreman_remote_execution_core/polling_script_runner.rb +6 -5
- data/lib/foreman_remote_execution_core/script_runner.rb +126 -21
- data/lib/foreman_remote_execution_core/version.rb +1 -1
- 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: 6c119b8c10a5f1c10cd6862e38b3cd926c022887c897583677a9d3ecc2fa62fa
|
4
|
+
data.tar.gz: 54295d5b48bfe55a17f32b9f599a23963f778a5d7ae3731e2a29001eae079528
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89101a702cc1be1ce3f471407659831c01dda578cc73c5d7c830a298eee15a0b390bc1f2f90d703328a6f054ec636f636b6bed3a2b5add49f73663a3cd2423c7
|
7
|
+
data.tar.gz: eb23b63b87f06fd5cec7ddaff021669a34370a0972e3e63b7c6799b2a89cdbdc870e63e51a9edcc4d02c70d6806058c321dffa5b60fbd8d435fea99d555f9c2a
|
@@ -11,7 +11,8 @@ module ForemanRemoteExecutionCore
|
|
11
11
|
:async_ssh => false,
|
12
12
|
# When set to nil, makes REX use the runner's default interval
|
13
13
|
:runner_refresh_interval => nil,
|
14
|
-
:ssh_log_level => :fatal
|
14
|
+
:ssh_log_level => :fatal,
|
15
|
+
:cleanup_working_dirs => true)
|
15
16
|
|
16
17
|
SSH_LOG_LEVELS = %w(debug info warn error fatal).freeze
|
17
18
|
|
@@ -8,7 +8,7 @@ module ForemanRemoteExecutionCore
|
|
8
8
|
:step_id => run_step_id,
|
9
9
|
:uuid => execution_plan_id
|
10
10
|
}
|
11
|
-
ForemanRemoteExecutionCore.runner_class.
|
11
|
+
ForemanRemoteExecutionCore.runner_class.build(input.merge(additional_options))
|
12
12
|
end
|
13
13
|
|
14
14
|
def runner_dispatcher
|
@@ -23,6 +23,10 @@ module ForemanRemoteExecutionCore
|
|
23
23
|
end
|
24
24
|
@data.freeze
|
25
25
|
end
|
26
|
+
|
27
|
+
def self.build(options)
|
28
|
+
new(options)
|
29
|
+
end
|
26
30
|
end
|
27
31
|
|
28
32
|
def initialize(*args)
|
@@ -72,7 +76,7 @@ module ForemanRemoteExecutionCore
|
|
72
76
|
# Decide if the execution should fail or not
|
73
77
|
def exit_code
|
74
78
|
fail_chance = ENV.fetch('REX_SIMULATE_FAIL_CHANCE', 0).to_i
|
75
|
-
fail_exitcode = ENV.fetch('REX_SIMULATE_EXIT', 0)
|
79
|
+
fail_exitcode = ENV.fetch('REX_SIMULATE_EXIT', 0).to_i
|
76
80
|
if fail_exitcode == 0 || fail_chance < (Random.rand * 100).round
|
77
81
|
0
|
78
82
|
else
|
@@ -3,8 +3,8 @@ module ForemanRemoteExecutionCore
|
|
3
3
|
|
4
4
|
DEFAULT_REFRESH_INTERVAL = 60
|
5
5
|
|
6
|
-
def initialize(options
|
7
|
-
super(options)
|
6
|
+
def initialize(options, user_method)
|
7
|
+
super(options, user_method)
|
8
8
|
@callback_host = options[:callback_host]
|
9
9
|
@task_id = options[:uuid]
|
10
10
|
@step_id = options[:step_id]
|
@@ -24,7 +24,7 @@ module ForemanRemoteExecutionCore
|
|
24
24
|
close_fds = close_stdin + ' >/dev/null 2>/dev/null'
|
25
25
|
# pipe the output to tee while capturing the exit code in a file, don't wait for it to finish, output PID of the main command
|
26
26
|
<<-SCRIPT.gsub(/^\s+\| /, '')
|
27
|
-
| sh -c '(#{
|
27
|
+
| sh -c '(#{@user_method.cli_command_prefix}#{@remote_script} #{close_stdin}; echo $?>#{@exit_code_path}) | /usr/bin/tee #{@output_path} >/dev/null; #{callback_scriptlet}' #{close_fds} &
|
28
28
|
| echo $! > '#{@pid_path}'
|
29
29
|
SCRIPT
|
30
30
|
end
|
@@ -36,7 +36,7 @@ module ForemanRemoteExecutionCore
|
|
36
36
|
def refresh
|
37
37
|
err = output = nil
|
38
38
|
with_retries do
|
39
|
-
_, output, err = run_sync("#{
|
39
|
+
_, output, err = run_sync("#{@user_method.cli_command_prefix} #{@retrieval_script}")
|
40
40
|
end
|
41
41
|
lines = output.lines
|
42
42
|
result = lines.shift.match(/^DONE (\d+)?/)
|
@@ -45,6 +45,7 @@ module ForemanRemoteExecutionCore
|
|
45
45
|
if result
|
46
46
|
exitcode = result[1] || 0
|
47
47
|
publish_exit_status(exitcode.to_i)
|
48
|
+
run_sync("rm -rf \"#{remote_command_dir}\"") if @cleanup_working_dirs
|
48
49
|
end
|
49
50
|
destroy_session
|
50
51
|
end
|
@@ -91,7 +92,7 @@ module ForemanRemoteExecutionCore
|
|
91
92
|
def callback_scriptlet(callback_script_path = nil)
|
92
93
|
if @otp
|
93
94
|
callback_script_path = cp_script_to_remote(callback_script, 'callback') if callback_script_path.nil?
|
94
|
-
"#{
|
95
|
+
"#{@user_method.cli_command_prefix}#{callback_script_path}"
|
95
96
|
else
|
96
97
|
':' # Shell synonym for "do nothing"
|
97
98
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'net/ssh'
|
2
|
+
require 'fileutils'
|
2
3
|
|
3
4
|
# rubocop:disable Lint/HandleExceptions
|
4
5
|
begin
|
@@ -7,6 +8,83 @@ rescue LoadError; end
|
|
7
8
|
# rubocop:enable Lint/HandleExceptions:
|
8
9
|
|
9
10
|
module ForemanRemoteExecutionCore
|
11
|
+
class SudoUserMethod
|
12
|
+
LOGIN_PROMPT = 'rex login: '.freeze
|
13
|
+
|
14
|
+
attr_reader :effective_user, :ssh_user, :effective_user_password, :password_sent
|
15
|
+
|
16
|
+
def initialize(effective_user, ssh_user, effective_user_password)
|
17
|
+
@effective_user = effective_user
|
18
|
+
@ssh_user = ssh_user
|
19
|
+
@effective_user_password = effective_user_password.to_s
|
20
|
+
@password_sent = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_data(received_data, ssh_channel)
|
24
|
+
if received_data.match(LOGIN_PROMPT)
|
25
|
+
ssh_channel.send_data(effective_user_password + "\n")
|
26
|
+
@password_sent = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def filter_password?(received_data)
|
31
|
+
!@effective_user_password.empty? && @password_sent && received_data.match(@effective_user_password)
|
32
|
+
end
|
33
|
+
|
34
|
+
def sent_all_data?
|
35
|
+
effective_user_password.empty? || password_sent
|
36
|
+
end
|
37
|
+
|
38
|
+
def cli_command_prefix
|
39
|
+
"sudo -p '#{LOGIN_PROMPT}' -u #{effective_user} "
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset
|
43
|
+
@password_sent = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class SuUserMethod
|
48
|
+
attr_accessor :effective_user, :ssh_user
|
49
|
+
|
50
|
+
def initialize(effective_user, ssh_user)
|
51
|
+
@effective_user = effective_user
|
52
|
+
@ssh_user = ssh_user
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_data(_, _); end
|
56
|
+
|
57
|
+
def filter_password?(received_data)
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def sent_all_data?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def cli_command_prefix
|
66
|
+
"su - #{effective_user} -c "
|
67
|
+
end
|
68
|
+
|
69
|
+
def reset; end
|
70
|
+
end
|
71
|
+
|
72
|
+
class NoopUserMethod
|
73
|
+
def on_data(_, _); end
|
74
|
+
|
75
|
+
def filter_password?(received_data)
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
def sent_all_data?
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def cli_command_prefix; end
|
84
|
+
|
85
|
+
def reset; end
|
86
|
+
end
|
87
|
+
|
10
88
|
class ScriptRunner < ForemanTasksCore::Runner::Base
|
11
89
|
attr_reader :execution_timeout_interval
|
12
90
|
|
@@ -14,7 +92,7 @@ module ForemanRemoteExecutionCore
|
|
14
92
|
DEFAULT_REFRESH_INTERVAL = 1
|
15
93
|
MAX_PROCESS_RETRIES = 4
|
16
94
|
|
17
|
-
def initialize(options)
|
95
|
+
def initialize(options, user_method)
|
18
96
|
super()
|
19
97
|
@host = options.fetch(:hostname)
|
20
98
|
@script = options.fetch(:script)
|
@@ -22,8 +100,6 @@ module ForemanRemoteExecutionCore
|
|
22
100
|
@ssh_port = options.fetch(:ssh_port, 22)
|
23
101
|
@ssh_password = options.fetch(:secrets, {}).fetch(:ssh_password, nil)
|
24
102
|
@key_passphrase = options.fetch(:secrets, {}).fetch(:key_passphrase, nil)
|
25
|
-
@effective_user = options.fetch(:effective_user, nil)
|
26
|
-
@effective_user_method = options.fetch(:effective_user_method, 'sudo')
|
27
103
|
@host_public_key = options.fetch(:host_public_key, nil)
|
28
104
|
@verify_host = options.fetch(:verify_host, nil)
|
29
105
|
@execution_timeout_interval = options.fetch(:execution_timeout_interval, nil)
|
@@ -31,6 +107,27 @@ module ForemanRemoteExecutionCore
|
|
31
107
|
@client_private_key_file = settings.fetch(:ssh_identity_key_file)
|
32
108
|
@local_working_dir = options.fetch(:local_working_dir, settings.fetch(:local_working_dir))
|
33
109
|
@remote_working_dir = options.fetch(:remote_working_dir, settings.fetch(:remote_working_dir))
|
110
|
+
@cleanup_working_dirs = options.fetch(:cleanup_working_dirs, settings.fetch(:cleanup_working_dirs))
|
111
|
+
@user_method = user_method
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.build(options)
|
115
|
+
effective_user = options.fetch(:effective_user, nil)
|
116
|
+
ssh_user = options.fetch(:ssh_user, 'root')
|
117
|
+
effective_user_method = options.fetch(:effective_user_method, 'sudo')
|
118
|
+
|
119
|
+
user_method = if effective_user.nil? || effective_user == ssh_user
|
120
|
+
NoopUserMethod.new
|
121
|
+
elsif effective_user_method == 'sudo'
|
122
|
+
SudoUserMethod.new(effective_user, ssh_user,
|
123
|
+
options.fetch(:secrets, {}).fetch(:sudo_password, nil))
|
124
|
+
elsif effective_user_method == 'su'
|
125
|
+
SuUserMethod.new(effective_user, ssh_user)
|
126
|
+
else
|
127
|
+
raise "effective_user_method '#{effective_user_method}' not supported"
|
128
|
+
end
|
129
|
+
|
130
|
+
new(options, user_method)
|
34
131
|
end
|
35
132
|
|
36
133
|
def start
|
@@ -57,7 +154,7 @@ module ForemanRemoteExecutionCore
|
|
57
154
|
# pipe the output to tee while capturing the exit code in a file
|
58
155
|
<<-SCRIPT.gsub(/^\s+\| /, '')
|
59
156
|
| sh <<WRAPPER
|
60
|
-
| (#{
|
157
|
+
| (#{@user_method.cli_command_prefix}#{@remote_script} < /dev/null; echo \\$?>#{@exit_code_path}) | /usr/bin/tee #{@output_path}
|
61
158
|
| exit \\$(cat #{@exit_code_path})
|
62
159
|
| WRAPPER
|
63
160
|
SCRIPT
|
@@ -122,7 +219,12 @@ module ForemanRemoteExecutionCore
|
|
122
219
|
end
|
123
220
|
|
124
221
|
def close
|
222
|
+
run_sync("rm -rf \"#{remote_command_dir}\"") if should_cleanup?
|
223
|
+
rescue => e
|
224
|
+
publish_exception('Error when removing remote working dir', e, false)
|
225
|
+
ensure
|
125
226
|
@session.close if @session && !@session.closed?
|
227
|
+
FileUtils.rm_rf(local_command_dir) if Dir.exist?(local_command_dir) && @cleanup_working_dirs
|
126
228
|
end
|
127
229
|
|
128
230
|
def publish_data(data, type)
|
@@ -131,6 +233,10 @@ module ForemanRemoteExecutionCore
|
|
131
233
|
|
132
234
|
private
|
133
235
|
|
236
|
+
def should_cleanup?
|
237
|
+
@session && !@session.closed? && @cleanup_working_dirs
|
238
|
+
end
|
239
|
+
|
134
240
|
def session
|
135
241
|
@session ||= begin
|
136
242
|
@logger.debug("opening session to #{@ssh_user}@#{@host}")
|
@@ -167,9 +273,14 @@ module ForemanRemoteExecutionCore
|
|
167
273
|
def run_async(command)
|
168
274
|
raise 'Async command already in progress' if @started
|
169
275
|
@started = false
|
276
|
+
@user_method.reset
|
277
|
+
|
170
278
|
session.open_channel do |channel|
|
171
279
|
channel.request_pty
|
172
|
-
channel.on_data
|
280
|
+
channel.on_data do |ch, data|
|
281
|
+
publish_data(data, 'stdout') unless @user_method.filter_password?(data)
|
282
|
+
@user_method.on_data(data, ch)
|
283
|
+
end
|
173
284
|
channel.on_extended_data { |ch, type, data| publish_data(data, 'stderr') }
|
174
285
|
# standard exit of the command
|
175
286
|
channel.on_request('exit-status') { |ch, data| publish_exit_status(data.read_long) }
|
@@ -181,15 +292,19 @@ module ForemanRemoteExecutionCore
|
|
181
292
|
# that the session is inactive
|
182
293
|
ch.wait
|
183
294
|
end
|
184
|
-
channel.exec(command) do |
|
295
|
+
channel.exec(command) do |_, success|
|
185
296
|
@started = true
|
186
297
|
raise('Error initializing command') unless success
|
187
298
|
end
|
188
299
|
end
|
189
|
-
session.process(0)
|
300
|
+
session.process(0) { !run_started? }
|
190
301
|
return true
|
191
302
|
end
|
192
303
|
|
304
|
+
def run_started?
|
305
|
+
@started && @user_method.sent_all_data?
|
306
|
+
end
|
307
|
+
|
193
308
|
def run_sync(command, stdin = nil)
|
194
309
|
stdout = ''
|
195
310
|
stderr = ''
|
@@ -197,7 +312,9 @@ module ForemanRemoteExecutionCore
|
|
197
312
|
started = false
|
198
313
|
|
199
314
|
channel = session.open_channel do |ch|
|
200
|
-
ch.on_data
|
315
|
+
ch.on_data do |c, data|
|
316
|
+
stdout.concat(data)
|
317
|
+
end
|
201
318
|
ch.on_extended_data { |_, _, data| stderr.concat(data) }
|
202
319
|
ch.on_request('exit-status') { |_, data| exit_status = data.read_long }
|
203
320
|
# Send data to stdin if we have some
|
@@ -213,25 +330,13 @@ module ForemanRemoteExecutionCore
|
|
213
330
|
started = true
|
214
331
|
end
|
215
332
|
end
|
216
|
-
session.process(0)
|
333
|
+
session.process(0) { !started }
|
217
334
|
# Closing the channel without sending any data gives us SIGPIPE
|
218
335
|
channel.close unless stdin.nil?
|
219
336
|
channel.wait
|
220
337
|
return exit_status, stdout, stderr
|
221
338
|
end
|
222
339
|
|
223
|
-
def su_prefix
|
224
|
-
return if @effective_user.nil? || @effective_user == @ssh_user
|
225
|
-
case @effective_user_method
|
226
|
-
when 'sudo'
|
227
|
-
"sudo -n -u #{@effective_user} "
|
228
|
-
when 'su'
|
229
|
-
"su - #{@effective_user} -c "
|
230
|
-
else
|
231
|
-
raise "effective_user_method ''#{@effective_user_method}'' not supported"
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
340
|
def prepare_known_hosts
|
236
341
|
path = local_command_file('known_hosts')
|
237
342
|
if @host_public_key
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution_core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Nečas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: foreman-tasks-core
|