foreman_remote_execution_core 1.0.5 → 1.0.6

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