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 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