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 +4 -4
- data/lib/foreman_remote_execution_core.rb +17 -6
- data/lib/foreman_remote_execution_core/actions.rb +9 -1
- data/lib/foreman_remote_execution_core/dispatcher.rb +12 -0
- data/lib/foreman_remote_execution_core/polling_script_runner.rb +125 -0
- data/lib/foreman_remote_execution_core/script_runner.rb +25 -14
- data/lib/foreman_remote_execution_core/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3da26d7438bb0f37e70f72eff0be70fa47601eaa
|
4
|
+
data.tar.gz: b23e6453a1df9b201e33653c1af7309421b06755
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
7
|
-
:ssh_user
|
8
|
-
:remote_working_dir
|
9
|
-
:local_working_dir
|
10
|
-
:kerberos_auth
|
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?
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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(
|
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)
|
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.
|
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-
|
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.
|
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.
|
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.
|
77
|
+
rubygems_version: 2.6.12
|
76
78
|
signing_key:
|
77
79
|
specification_version: 4
|
78
80
|
summary: Foreman remote execution - core bits
|