foreman_remote_execution_core 1.0.2 → 1.0.3

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
  SHA1:
3
- metadata.gz: 40dc9b4977c799a3f8ad7bccb38fc4b8cc32134b
4
- data.tar.gz: 5ae9fe01a1989c2d2cfc9d68d4eae023e5cdc903
3
+ metadata.gz: 3216a936b41179b58d8ae81a238d2fbdaff11e56
4
+ data.tar.gz: aec1f9700b5a9f7daa4fbe6259dc8e2d64943b30
5
5
  SHA512:
6
- metadata.gz: 6b3c4731a69062ca30bbc023abe2aed22f2e9be5d7ab7e067fc8c14133cb57c16f5fe70c4bceaf10abafe9717755139e246f470e08e28ba69f67b6aeb711be50
7
- data.tar.gz: da984876ee96f01b82d2b6f53091e8346a5a87863cb6235b35d5cb7f145bde1b99a9e99765cba67fe4cd922c655082daa8db02b81f028e4b2c26e4e766ba93e4
6
+ metadata.gz: 2eadacdd4acfd582061150991734a46ac53909874379c0cf7f4562089aa32342511875773c84134c97f2e2910ae9bd94aeb21c6726c0a7652dc93ccdb808a282
7
+ data.tar.gz: 6f6666aedd9474440e52a15f2927e5e279e439e75fb475e8bb9b747b921d83cb003a978b50d80596858845c06d581c51e68d59f1fc75697264a4e062cc8a8490
@@ -8,9 +8,22 @@ module ForemanRemoteExecutionCore
8
8
  :remote_working_dir => '/var/tmp',
9
9
  :local_working_dir => '/var/tmp')
10
10
 
11
+ def self.simulate?
12
+ %w(yes true 1).include? ENV.fetch('REX_SIMULATE', '').downcase
13
+ end
14
+
15
+ def self.runner_class
16
+ @runner_class ||= simulate? ? FakeScriptRunner : ScriptRunner
17
+ end
18
+
11
19
  if ForemanTasksCore.dynflow_present?
12
20
  require 'foreman_tasks_core/runner'
13
- require 'foreman_remote_execution_core/script_runner'
21
+ if simulate?
22
+ # Load the fake implementation of the script runner if debug is enabled
23
+ require 'foreman_remote_execution_core/fake_script_runner'
24
+ else
25
+ require 'foreman_remote_execution_core/script_runner'
26
+ end
14
27
  require 'foreman_remote_execution_core/actions'
15
28
  end
16
29
 
@@ -4,7 +4,7 @@ module ForemanRemoteExecutionCore
4
4
  module Actions
5
5
  class RunScript < ForemanTasksCore::Runner::Action
6
6
  def initiate_runner
7
- ScriptRunner.new(input)
7
+ ForemanRemoteExecutionCore.runner_class.new(input)
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,82 @@
1
+ module ForemanRemoteExecutionCore
2
+ class FakeScriptRunner < ForemanTasksCore::Runner::Base
3
+
4
+ @data = []
5
+
6
+ class << self
7
+ attr_accessor :data
8
+
9
+ def load_data(path = nil)
10
+ if path.nil?
11
+ @data = <<-END.gsub(/^\s+\| ?/, '').lines
12
+ | ====== Simulated Remote Execution ======
13
+ |
14
+ | This is an output of a simulated remote
15
+ | execution run. It should run for about
16
+ | 5 seconds and finish successfully.
17
+ END
18
+ else
19
+ File.open(File.expand_path(path), 'r') do |f|
20
+ @data = f.readlines.map(&:chomp)
21
+ end
22
+ end
23
+ @data.freeze
24
+ end
25
+ end
26
+
27
+ def initialize(*args)
28
+ super
29
+ # Load the fake output the first time its needed
30
+ self.class.load_data(ENV['REX_SIMULATE_PATH']) unless self.class.data.frozen?
31
+ @position = 0
32
+ end
33
+
34
+ def start
35
+ refresh
36
+ end
37
+
38
+ # Do one step
39
+ def refresh
40
+ if done?
41
+ finish
42
+ else
43
+ step
44
+ end
45
+ end
46
+
47
+ def kill
48
+ finish
49
+ end
50
+
51
+ private
52
+
53
+ def finish
54
+ publish_exit_status exit_code
55
+ end
56
+
57
+ def step
58
+ publish_data(next_chunk, 'stdout')
59
+ end
60
+
61
+ def done?
62
+ @position == self.class.data.count
63
+ end
64
+
65
+ def next_chunk
66
+ output = self.class.data[@position]
67
+ @position += 1
68
+ output
69
+ end
70
+
71
+ # Decide if the execution should fail or not
72
+ def exit_code
73
+ fail_chance = ENV.fetch('REX_SIMULATE_FAIL_CHANCE', 0).to_i
74
+ fail_exitcode = ENV.fetch('REX_SIMULATE_EXIT', 0)
75
+ if fail_exitcode == 0 || fail_chance < (Random.rand * 100).round
76
+ 0
77
+ else
78
+ fail_exitcode
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,5 +1,4 @@
1
1
  require 'net/ssh'
2
- require 'net/scp'
3
2
 
4
3
  module ForemanRemoteExecutionCore
5
4
  class ScriptRunner < ForemanTasksCore::Runner::Base
@@ -28,7 +27,7 @@ module ForemanRemoteExecutionCore
28
27
 
29
28
  # pipe the output to tee while capturing the exit code in a file
30
29
  script = <<-SCRIPT
31
- (#{su_prefix}#{remote_script}; echo $?>#{exit_code_path}) | /usr/bin/tee #{output_path}
30
+ (#{su_prefix}#{remote_script} < /dev/null; echo $?>#{exit_code_path}) | /usr/bin/tee #{output_path}
32
31
  exit $(< #{exit_code_path})
33
32
  SCRIPT
34
33
 
@@ -148,13 +147,18 @@ module ForemanRemoteExecutionCore
148
147
  return true
149
148
  end
150
149
 
151
- def run_sync(command)
152
- output = ""
150
+ def run_sync(command, stdin = nil)
151
+ stdout = ""
152
+ stderr = ""
153
153
  exit_status = nil
154
+ started = false
155
+
154
156
  channel = session.open_channel do |ch|
155
- ch.on_data { |data| output.concat(data) }
156
- ch.on_extended_data { |_, _, data| output.concat(data) }
157
+ ch.on_data { |_, data| stdout.concat(data) }
158
+ ch.on_extended_data { |_, _, data| stderr.concat(data) }
157
159
  ch.on_request("exit-status") { |_, data| exit_status = data.read_long }
160
+ # Send data to stdin if we have some
161
+ ch.send_data(stdin) unless stdin.nil?
158
162
  # on signal: sending the signal value (such as 'TERM')
159
163
  ch.on_request("exit-signal") do |_, data|
160
164
  exit_status = data.read_string
@@ -163,10 +167,14 @@ module ForemanRemoteExecutionCore
163
167
  end
164
168
  ch.exec command do |_, success|
165
169
  raise "could not execute command" unless success
170
+ started = true
166
171
  end
167
172
  end
173
+ session.process(0) until started
174
+ # Closing the channel without sending any data gives us SIGPIPE
175
+ channel.close unless stdin.nil?
168
176
  channel.wait
169
- return exit_status, output
177
+ return exit_status, stdout, stderr
170
178
  end
171
179
 
172
180
  def su_prefix
@@ -215,29 +223,36 @@ module ForemanRemoteExecutionCore
215
223
  end
216
224
 
217
225
  def cp_script_to_remote
218
- local_script_file = write_command_file_locally('script', sanitize_script(@script))
219
- File.chmod(0555, local_script_file)
220
- remote_script_file = remote_command_file('script')
221
- upload_file(local_script_file, remote_script_file)
222
- return remote_script_file
226
+ upload_data(sanitize_script(@script), remote_command_file('script'), 555)
223
227
  end
224
228
 
225
- def upload_file(local_path, remote_path)
226
- ensure_remote_directory(File.dirname(remote_path))
227
- scp = Net::SCP.new(session)
228
- upload_channel = scp.upload(local_path, remote_path)
229
- upload_channel.wait
230
- ensure
231
- if upload_channel
232
- upload_channel.close
233
- upload_channel.wait
229
+ def upload_data(data, path, permissions = 555)
230
+ ensure_remote_directory File.dirname(path)
231
+ # We use tee here to pipe stdin coming from ssh to a file at $path, while silencing its output
232
+ # This is used to write to $path with elevated permissions, solutions using cat and output redirection
233
+ # would not work, because the redirection would happen in the non-elevated shell.
234
+ command = "#{su_prefix} tee '#{path}' >/dev/null && #{su_prefix} chmod '#{permissions}' '#{path}'"
235
+
236
+ @logger.debug("Sending data to #{path} on remote host:\n#{data}")
237
+ status, _out, err = run_sync(command, data)
238
+
239
+ @logger.warn("Output on stderr while uploading #{path}:\n#{err}") unless err.empty?
240
+ if status != 0
241
+ raise "Unable to upload file to #{path} on remote system: exit code: #{status}"
234
242
  end
243
+ path
244
+ end
245
+
246
+ def upload_file(local_path, remote_path)
247
+ mode = File.stat(local_path).mode.to_s(8)[-3..-1]
248
+ @logger.debug('Uploading local file: #{local_path} as #{remote_path} with #{mode} permissions')
249
+ upload_data(File.read(local_path), remote_path, mode)
235
250
  end
236
251
 
237
252
  def ensure_remote_directory(path)
238
- exit_code, output = run_sync("mkdir -p #{path}")
253
+ exit_code, _output, err = run_sync("mkdir -p #{path}")
239
254
  if exit_code != 0
240
- raise "Unable to create directory on remote system #{path}: exit code: #{exit_code}\n #{output}"
255
+ raise "Unable to create directory on remote system #{path}: exit code: #{exit_code}\n #{err}"
241
256
  end
242
257
  end
243
258
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecutionCore
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.3'
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.0.2
4
+ version: 1.0.3
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: 2016-11-30 00:00:00.000000000 Z
11
+ date: 2017-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -52,8 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description: |2
56
- Ssh remote execution provider code sharable between Foreman and Foreman-Proxy
55
+ description: " Ssh remote execution provider code sharable between Foreman and Foreman-Proxy\n"
57
56
  email:
58
57
  - inecas@redhat.com
59
58
  executables: []
@@ -63,6 +62,7 @@ files:
63
62
  - LICENSE
64
63
  - lib/foreman_remote_execution_core.rb
65
64
  - lib/foreman_remote_execution_core/actions.rb
65
+ - lib/foreman_remote_execution_core/fake_script_runner.rb
66
66
  - lib/foreman_remote_execution_core/script_runner.rb
67
67
  - lib/foreman_remote_execution_core/version.rb
68
68
  homepage: https://github.com/theforeman/foreman_remote_execution
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  version: '0'
86
86
  requirements: []
87
87
  rubyforge_project:
88
- rubygems_version: 2.4.5
88
+ rubygems_version: 2.5.1
89
89
  signing_key:
90
90
  specification_version: 4
91
91
  summary: Foreman remote execution - core bits