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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f58fc844cebc176f015343ed6c5ed47a97ffeac65356c8eb34009705b29322dd
4
- data.tar.gz: 91b3890c6238578942389be52d1392a140deba33aa6261de65776788bca86fb7
3
+ metadata.gz: 6c119b8c10a5f1c10cd6862e38b3cd926c022887c897583677a9d3ecc2fa62fa
4
+ data.tar.gz: 54295d5b48bfe55a17f32b9f599a23963f778a5d7ae3731e2a29001eae079528
5
5
  SHA512:
6
- metadata.gz: 86b4e7b8e098b493cda6214cfd5fa35ed471221c7ef8af9ba23a8966f7eca0236aea68eac9890d1333119f954e4b90b730fd2e1f73ad05c1b97f761e936bebad
7
- data.tar.gz: cfb6aba18b777bb140e2535bd16c70a6c248ff7d4b0a44fcc9005da32c647321bcf3517d5f6fef2b2ae8377105d0209ddd7e65da0b04b6b332a138d4e297ca70
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.new(input.merge(additional_options))
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 '(#{su_prefix}#{@remote_script} #{close_stdin}; echo $?>#{@exit_code_path}) | /usr/bin/tee #{@output_path} >/dev/null; #{callback_scriptlet}' #{close_fds} &
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("#{su_prefix} #{@retrieval_script}")
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
- "#{su_prefix}#{callback_script_path}"
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
- | (#{su_prefix}#{@remote_script} < /dev/null; echo \\$?>#{@exit_code_path}) | /usr/bin/tee #{@output_path}
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 { |ch, data| publish_data(data, 'stdout') }
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 |ch, success|
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) until @started
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 { |_, data| stdout.concat(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) until started
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
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecutionCore
2
- VERSION = '1.1.1'.freeze
2
+ VERSION = '1.1.2'.freeze
3
3
  end
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.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-03-12 00:00:00.000000000 Z
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