foreman_remote_execution_core 1.0.2 → 1.0.3

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