foreman_remote_execution_core 1.0.5 → 1.0.6

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: a81b2a41e08c662ce352dd037d625bd42cb9d53a
4
- data.tar.gz: a5294079e9132731210ebe670f2f7c1810639cdb
3
+ metadata.gz: 3da26d7438bb0f37e70f72eff0be70fa47601eaa
4
+ data.tar.gz: b23e6453a1df9b201e33653c1af7309421b06755
5
5
  SHA512:
6
- metadata.gz: 04e246d897664d62109be19680cb4c973dac71868385b228f1257de2d923a8523fc12d9cdbbb2c167de092f8703fcea26459d0363ac8f005344d40a1afe8b484
7
- data.tar.gz: 8ab7a7eb9c9accc486c975896aaf70f3671e95ca1ab2f9509d56832c6985185e45a74c0b5a4fe7fef11aa4c9f30e97ffca62d995889e16b8d03967c157a871d5
6
+ metadata.gz: 4c7cdca98c404df5cd8e45aaf944044a611161b7de2d75373846bf0968cb1cfcf33b9af3185288b5e4cc70ad92e7bd68ce5a652cd981700e85c2dbef399f6489
7
+ data.tar.gz: 5acc08e7302f5f58b27129980fcc39f4e98ec7c7dfc82877c1fa466a1039124d42f1dede0835319f12c8b5c34419b3e01ba8b17581d14a4ec2743c5527845b78
@@ -3,18 +3,27 @@ require 'foreman_tasks_core'
3
3
  module ForemanRemoteExecutionCore
4
4
  extend ForemanTasksCore::SettingsLoader
5
5
  register_settings([:remote_execution_ssh, :smart_proxy_remote_execution_ssh_core],
6
- :ssh_identity_key_file => '~/.ssh/id_rsa_foreman_proxy',
7
- :ssh_user => 'root',
8
- :remote_working_dir => '/var/tmp',
9
- :local_working_dir => '/var/tmp',
10
- :kerberos_auth => false)
6
+ :ssh_identity_key_file => '~/.ssh/id_rsa_foreman_proxy',
7
+ :ssh_user => 'root',
8
+ :remote_working_dir => '/var/tmp',
9
+ :local_working_dir => '/var/tmp',
10
+ :kerberos_auth => false,
11
+ :async_ssh => false,
12
+ # When set to nil, makes REX use the runner's default interval
13
+ :runner_refresh_interval => nil)
11
14
 
12
15
  def self.simulate?
13
16
  %w(yes true 1).include? ENV.fetch('REX_SIMULATE', '').downcase
14
17
  end
15
18
 
16
19
  def self.runner_class
17
- @runner_class ||= simulate? ? FakeScriptRunner : ScriptRunner
20
+ @runner_class ||= if simulate?
21
+ FakeScriptRunner
22
+ elsif settings[:async_ssh]
23
+ PollingScriptRunner
24
+ else
25
+ ScriptRunner
26
+ end
18
27
  end
19
28
 
20
29
  if ForemanTasksCore.dynflow_present?
@@ -24,7 +33,9 @@ module ForemanRemoteExecutionCore
24
33
  require 'foreman_remote_execution_core/fake_script_runner'
25
34
  else
26
35
  require 'foreman_remote_execution_core/script_runner'
36
+ require 'foreman_remote_execution_core/polling_script_runner'
27
37
  end
38
+ require 'foreman_remote_execution_core/dispatcher'
28
39
  require 'foreman_remote_execution_core/actions'
29
40
  end
30
41
 
@@ -4,7 +4,15 @@ module ForemanRemoteExecutionCore
4
4
  module Actions
5
5
  class RunScript < ForemanTasksCore::Runner::Action
6
6
  def initiate_runner
7
- ForemanRemoteExecutionCore.runner_class.new(input)
7
+ additional_options = {
8
+ :step_id => run_step_id,
9
+ :uuid => execution_plan_id
10
+ }
11
+ ForemanRemoteExecutionCore.runner_class.new(input.merge(additional_options))
12
+ end
13
+
14
+ def runner_dispatcher
15
+ ForemanRemoteExecutionCore::Dispatcher.instance
8
16
  end
9
17
  end
10
18
  end
@@ -0,0 +1,12 @@
1
+ require 'foreman_tasks_core/runner/dispatcher'
2
+
3
+ module ForemanRemoteExecutionCore
4
+ class Dispatcher < ::ForemanTasksCore::Runner::Dispatcher
5
+
6
+ def refresh_interval
7
+ @refresh_interval ||= ForemanRemoteExecutionCore.settings[:runner_refresh_interval] ||
8
+ ForemanRemoteExecutionCore.runner_class::DEFAULT_REFRESH_INTERVAL
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,125 @@
1
+ module ForemanRemoteExecutionCore
2
+ class PollingScriptRunner < ScriptRunner
3
+
4
+ DEFAULT_REFRESH_INTERVAL = 60
5
+
6
+ def initialize(options = {})
7
+ super(options)
8
+ @callback_host = options[:callback_host]
9
+ @task_id = options[:uuid]
10
+ @step_id = options[:step_id]
11
+ @otp = ForemanTasksCore::OtpManager.generate_otp(@task_id)
12
+ end
13
+
14
+ def prepare_start
15
+ super
16
+ basedir = File.dirname @remote_script
17
+ @pid_path = File.join(basedir, 'pid')
18
+ @retrieval_script ||= File.join(basedir, 'retrieve')
19
+ prepare_retrieval unless @prepared
20
+ end
21
+
22
+ def control_script
23
+ close_stdin = '</dev/null'
24
+ close_fds = close_stdin + ' >/dev/null 2>/dev/null'
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
+ <<-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} &
28
+ | echo $! > '#{@pid_path}'
29
+ SCRIPT
30
+ end
31
+
32
+ def trigger(*args)
33
+ run_sync(*args)
34
+ end
35
+
36
+ def refresh
37
+ err = output = nil
38
+ with_retries do
39
+ _, output, err = run_sync("#{su_prefix} #{@retrieval_script}")
40
+ end
41
+ lines = output.lines
42
+ result = lines.shift.match(/^DONE (\d+)?/)
43
+ publish_data(lines.join, 'stdout') unless lines.empty?
44
+ publish_data(err, 'stderr') unless err.empty?
45
+ if result
46
+ exitcode = result[1] || 0
47
+ publish_exit_status(exitcode.to_i)
48
+ end
49
+ destroy_session
50
+ end
51
+
52
+ def close
53
+ super
54
+ ForemanTasksCore::OtpManager.drop_otp(@task_id, @otp) if @otp
55
+ end
56
+
57
+ def prepare_retrieval
58
+ # The script always outputs at least one line
59
+ # First line of the output either has to begin with
60
+ # "RUNNING" or "DONE $EXITCODE"
61
+ # The following lines are treated as regular output
62
+ base = File.dirname(@output_path)
63
+ posfile = File.join(base, 'position')
64
+ tmpfile = File.join(base, 'tmp')
65
+ script = <<-SCRIPT.gsub(/^ +\| /, '')
66
+ | #!/bin/sh
67
+ | pid=$(cat "#{@pid_path}")
68
+ | if ! pgrep --help 2>/dev/null >/dev/null; then
69
+ | echo DONE 1
70
+ | echo "pgrep is required" >&2
71
+ | exit 1
72
+ | fi
73
+ | if pgrep -P "$pid" >/dev/null 2>/dev/null; then
74
+ | echo RUNNING
75
+ | else
76
+ | echo "DONE $(cat "#{@exit_code_path}" 2>/dev/null)"
77
+ | fi
78
+ | [ -f "#{@output_path}" ] || exit 0
79
+ | [ -f "#{posfile}" ] || echo 1 > "#{posfile}"
80
+ | position=$(cat "#{posfile}")
81
+ | tail --bytes "+${position}" "#{@output_path}" > "#{tmpfile}"
82
+ | bytes=$(cat "#{tmpfile}" | wc --bytes)
83
+ | expr "${position}" + "${bytes}" > "#{posfile}"
84
+ | cat "#{tmpfile}"
85
+ SCRIPT
86
+ @logger.debug("copying script:\n#{script.lines.map { |line| " | #{line}" }.join}")
87
+ cp_script_to_remote(script, 'retrieve')
88
+ @prepared = true
89
+ end
90
+
91
+ def callback_scriptlet(callback_script_path = nil)
92
+ if @otp
93
+ callback_script_path = cp_script_to_remote(callback_script, 'callback') if callback_script_path.nil?
94
+ "#{su_prefix}#{callback_script_path}"
95
+ else
96
+ ':' # Shell synonym for "do nothing"
97
+ end
98
+ end
99
+
100
+ def callback_script
101
+ <<-SCRIPT.gsub(/^ +\| /, '')
102
+ | #!/bin/sh
103
+ | exit_code=$(cat "#{@exit_code_path}")
104
+ | url="#{@callback_host}/dynflow/tasks/#{@task_id}/done"
105
+ | json="{ \\\"step_id\\\": #{@step_id} }"
106
+ | if which curl >/dev/null; then
107
+ | curl -X POST -d "$json" -u "#{@task_id}:#{@otp}" "$url"
108
+ | else
109
+ | echo 'curl is required' >&2
110
+ | exit 1
111
+ | fi
112
+ SCRIPT
113
+ end
114
+
115
+ private
116
+
117
+ def destroy_session
118
+ if @session
119
+ @logger.debug("Closing session with #{@ssh_user}@#{@host}")
120
+ @session.close
121
+ @session = nil
122
+ end
123
+ end
124
+ end
125
+ end
@@ -11,6 +11,7 @@ module ForemanRemoteExecutionCore
11
11
  attr_reader :execution_timeout_interval
12
12
 
13
13
  EXPECTED_POWER_ACTION_MESSAGES = ['restart host', 'shutdown host'].freeze
14
+ DEFAULT_REFRESH_INTERVAL = 1
14
15
 
15
16
  def initialize(options)
16
17
  super()
@@ -30,23 +31,33 @@ module ForemanRemoteExecutionCore
30
31
  end
31
32
 
32
33
  def start
33
- remote_script = cp_script_to_remote
34
- output_path = File.join(File.dirname(remote_script), 'output')
35
- exit_code_path = File.join(File.dirname(remote_script), 'exit_code')
34
+ prepare_start
35
+ script = control_script
36
+ logger.debug("executing script:\n#{script.lines.map { |line| " | #{line}" }.join}")
37
+ trigger(script)
38
+ rescue => e
39
+ logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
40
+ publish_exception('Error initializing command', e)
41
+ end
42
+
43
+ def trigger(*args)
44
+ run_async(*args)
45
+ end
36
46
 
47
+ def prepare_start
48
+ @remote_script = cp_script_to_remote
49
+ @output_path = File.join(File.dirname(@remote_script), 'output')
50
+ @exit_code_path = File.join(File.dirname(@remote_script), 'exit_code')
51
+ end
52
+
53
+ def control_script
37
54
  # pipe the output to tee while capturing the exit code in a file
38
- script = <<-SCRIPT.gsub(/^\s+\| /, '')
55
+ <<-SCRIPT.gsub(/^\s+\| /, '')
39
56
  | sh <<WRAPPER
40
- | (#{su_prefix}#{remote_script} < /dev/null; echo \\$?>#{exit_code_path}) | /usr/bin/tee #{output_path}
41
- | exit \\$(cat #{exit_code_path})
57
+ | (#{su_prefix}#{@remote_script} < /dev/null; echo \\$?>#{@exit_code_path}) | /usr/bin/tee #{@output_path}
58
+ | exit \\$(cat #{@exit_code_path})
42
59
  | WRAPPER
43
60
  SCRIPT
44
-
45
- logger.debug("executing script:\n#{script.lines.map { |line| " | #{line}" }.join}")
46
- run_async(script)
47
- rescue => e
48
- logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
49
- publish_exception('Error initializing command', e)
50
61
  end
51
62
 
52
63
  def refresh
@@ -246,8 +257,8 @@ module ForemanRemoteExecutionCore
246
257
  return path
247
258
  end
248
259
 
249
- def cp_script_to_remote
250
- upload_data(sanitize_script(@script), remote_command_file('script'), 555)
260
+ def cp_script_to_remote(script = @script, name = 'script')
261
+ upload_data(sanitize_script(script), remote_command_file(name), 555)
251
262
  end
252
263
 
253
264
  def upload_data(data, path, permissions = 555)
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecutionCore
2
- VERSION = '1.0.5'.freeze
2
+ VERSION = '1.0.6'.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.0.5
4
+ version: 1.0.6
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: 2017-07-11 00:00:00.000000000 Z
11
+ date: 2017-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.3
19
+ version: 0.1.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.3
26
+ version: 0.1.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: net-ssh
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -49,7 +49,9 @@ files:
49
49
  - LICENSE
50
50
  - lib/foreman_remote_execution_core.rb
51
51
  - lib/foreman_remote_execution_core/actions.rb
52
+ - lib/foreman_remote_execution_core/dispatcher.rb
52
53
  - lib/foreman_remote_execution_core/fake_script_runner.rb
54
+ - lib/foreman_remote_execution_core/polling_script_runner.rb
53
55
  - lib/foreman_remote_execution_core/script_runner.rb
54
56
  - lib/foreman_remote_execution_core/version.rb
55
57
  homepage: https://github.com/theforeman/foreman_remote_execution
@@ -72,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
74
  version: '0'
73
75
  requirements: []
74
76
  rubyforge_project:
75
- rubygems_version: 2.4.5
77
+ rubygems_version: 2.6.12
76
78
  signing_key:
77
79
  specification_version: 4
78
80
  summary: Foreman remote execution - core bits