foreman_remote_execution_core 1.1.1 → 1.1.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/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
|