smart_proxy_remote_execution_ssh 0.0.3 → 0.0.4
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 +13 -5
- data/bundler.d/Gemfile.local.rb +1 -0
- data/lib/smart_proxy_remote_execution_ssh/command_action.rb +1 -1
- data/lib/smart_proxy_remote_execution_ssh/command_update.rb +73 -0
- data/lib/smart_proxy_remote_execution_ssh/connector.rb +7 -53
- data/lib/smart_proxy_remote_execution_ssh/dispatcher.rb +39 -178
- data/lib/smart_proxy_remote_execution_ssh/session.rb +169 -0
- data/lib/smart_proxy_remote_execution_ssh/version.rb +1 -1
- data/lib/smart_proxy_remote_execution_ssh.rb +2 -0
- metadata +30 -25
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTEyZjNlZjgzOGU2ODkzNmYyMGM1NTgwNWZjM2VjZmU2NDljZmEwMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YjVmNmNiNDdiNTA5ZTU1YjNiNzNkMzcxOTkxZDY3YTgwN2QxYjBhNQ==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NmJmOTc3Njg3MmE5Yzk0NWQ0MjI3MmMwYjM0Mjk3Mzg3NWYxZDY2ZWI0YzNi
|
10
|
+
MjE1MDBkMjMwYTc0N2FlYjUyY2Q1MzgwZTQ0NzA5YzIwYmUyNTBlNzQ0NzI1
|
11
|
+
OTZhOGU5MWMyZGU5MzE1YjI4MzhmMWIwYjg5YzNmNTkzNGVkMjM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWRjNDU2YzgzNzAxM2NlMDliYTY3MTE5MDM3OTRkN2NiMzVmNmVlNTgxNTE4
|
14
|
+
YTcyMjY5YTJlZmE0NTkxYTJlOGNhM2ZmODEzZDE4ZWIwOTU4NDIyN2U2ZjFj
|
15
|
+
MWZhNTMzNDNkN2Y5ZDU4ZDFhNmU0ZTA4NWRlNGI1YjRhODU3NjE=
|
@@ -0,0 +1 @@
|
|
1
|
+
gem 'pry'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Proxy::RemoteExecution::Ssh
|
2
|
+
# update sent back to the suspended action
|
3
|
+
class CommandUpdate
|
4
|
+
attr_reader :buffer, :exit_status
|
5
|
+
|
6
|
+
def initialize(buffer)
|
7
|
+
@buffer = buffer
|
8
|
+
extract_exit_status
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract_exit_status
|
12
|
+
@buffer.delete_if do |data|
|
13
|
+
if data.is_a? StatusData
|
14
|
+
@exit_status = data.data
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def buffer_to_hash
|
21
|
+
buffer.map(&:to_hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.encode_exception(description, exception, fatal = true)
|
25
|
+
ret = [DebugData.new("#{description}\n#{exception.class} #{exception.message}")]
|
26
|
+
ret << StatusData.new('EXCEPTION') if fatal
|
27
|
+
return ret
|
28
|
+
end
|
29
|
+
|
30
|
+
class Data
|
31
|
+
attr_reader :data, :timestamp
|
32
|
+
|
33
|
+
def initialize(data, timestamp = Time.now)
|
34
|
+
@data = data
|
35
|
+
@timestamp = timestamp
|
36
|
+
end
|
37
|
+
|
38
|
+
def data_type
|
39
|
+
raise NotImplemented
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_hash
|
43
|
+
{ :output_type => data_type,
|
44
|
+
:output => data,
|
45
|
+
:timestamp => timestamp.to_f }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class StdoutData < Data
|
50
|
+
def data_type
|
51
|
+
:stdout
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class StderrData < Data
|
56
|
+
def data_type
|
57
|
+
:stderr
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class DebugData < Data
|
62
|
+
def data_type
|
63
|
+
:debug
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class StatusData < Data
|
68
|
+
def data_type
|
69
|
+
:status
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -6,49 +6,6 @@ module Proxy::RemoteExecution::Ssh
|
|
6
6
|
# Dynflow action. It runs just one (actor) thread for all the commands
|
7
7
|
# running in the system and updates the Dynflow actions periodically.
|
8
8
|
class Connector
|
9
|
-
class Data
|
10
|
-
attr_reader :data, :timestamp
|
11
|
-
|
12
|
-
def initialize(data, timestamp = Time.now)
|
13
|
-
@data = data
|
14
|
-
@timestamp = timestamp
|
15
|
-
end
|
16
|
-
|
17
|
-
def data_type
|
18
|
-
raise NotImplemented
|
19
|
-
end
|
20
|
-
|
21
|
-
def to_hash
|
22
|
-
{ :output_type => data_type,
|
23
|
-
:output => data,
|
24
|
-
:timestamp => timestamp.to_f }
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class StdoutData < Data
|
29
|
-
def data_type
|
30
|
-
:stdout
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class StderrData < Data
|
35
|
-
def data_type
|
36
|
-
:stderr
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class DebugData < Data
|
41
|
-
def data_type
|
42
|
-
:debug
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class StatusData < Data
|
47
|
-
def data_type
|
48
|
-
:status
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
9
|
MAX_PROCESS_RETRIES = 3
|
53
10
|
|
54
11
|
def initialize(host, user, options = {})
|
@@ -65,16 +22,16 @@ module Proxy::RemoteExecution::Ssh
|
|
65
22
|
def async_run(command)
|
66
23
|
started = false
|
67
24
|
session.open_channel do |channel|
|
68
|
-
channel.on_data { |ch, data| yield StdoutData.new(data) }
|
25
|
+
channel.on_data { |ch, data| yield CommandUpdate::StdoutData.new(data) }
|
69
26
|
|
70
|
-
channel.on_extended_data { |ch, type, data| yield StderrData.new(data) }
|
27
|
+
channel.on_extended_data { |ch, type, data| yield CommandUpdate::StderrData.new(data) }
|
71
28
|
|
72
29
|
# standard exit of the command
|
73
|
-
channel.on_request("exit-status") { |ch, data| yield StatusData.new(data.read_long) }
|
30
|
+
channel.on_request("exit-status") { |ch, data| yield CommandUpdate::StatusData.new(data.read_long) }
|
74
31
|
|
75
32
|
# on signal: sedning the signal value (such as 'TERM')
|
76
33
|
channel.on_request("exit-signal") do |ch, data|
|
77
|
-
yield(StatusData.new(data.read_string))
|
34
|
+
yield(CommandUpdate::StatusData.new(data.read_string))
|
78
35
|
ch.close
|
79
36
|
# wait for the channel to finish so that we know at the end
|
80
37
|
# that the session is inactive
|
@@ -84,8 +41,9 @@ module Proxy::RemoteExecution::Ssh
|
|
84
41
|
channel.exec(command) do |ch, success|
|
85
42
|
started = true
|
86
43
|
unless success
|
87
|
-
|
88
|
-
|
44
|
+
CommandUpdate.encode_exception("Error initializing command #{command}", e).each do |data|
|
45
|
+
yield data
|
46
|
+
end
|
89
47
|
end
|
90
48
|
end
|
91
49
|
end
|
@@ -155,10 +113,6 @@ module Proxy::RemoteExecution::Ssh
|
|
155
113
|
end
|
156
114
|
end
|
157
115
|
|
158
|
-
def inactive?
|
159
|
-
@session.nil? || @session.channels.empty?
|
160
|
-
end
|
161
|
-
|
162
116
|
def close
|
163
117
|
@logger.debug("closing session to #{@user}@#{@host}")
|
164
118
|
@session.close unless @session.nil? || @session.closed?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'smart_proxy_remote_execution_ssh/
|
1
|
+
require 'smart_proxy_remote_execution_ssh/session'
|
2
2
|
|
3
3
|
module Proxy::RemoteExecution::Ssh
|
4
4
|
# Service that handles running external commands for Actions::Command
|
@@ -28,204 +28,65 @@ module Proxy::RemoteExecution::Ssh
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
# update sent back to the suspended action
|
32
|
-
class CommandUpdate
|
33
|
-
attr_reader :buffer, :exit_status
|
34
|
-
|
35
|
-
def initialize(buffer, exit_status)
|
36
|
-
@buffer = buffer
|
37
|
-
@exit_status = exit_status
|
38
|
-
end
|
39
|
-
|
40
|
-
def buffer_to_hash
|
41
|
-
buffer.map(&:to_hash)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
31
|
def initialize(options = {})
|
46
32
|
@clock = options[:clock] || Dynflow::Clock.spawn('proxy-dispatcher-clock')
|
47
33
|
@logger = options[:logger] || Logger.new($stderr)
|
48
|
-
@connector_class = options[:connector_class] || Connector
|
49
|
-
@local_working_dir = options[:local_working_dir] || '/tmp/foreman-proxy-ssh/server'
|
50
|
-
@remote_working_dir = options[:remote_working_dir] || '/tmp/foreman-proxy-ssh/client'
|
51
|
-
@refresh_interval = options[:refresh_interval] || 1
|
52
|
-
@client_private_key_file = Proxy::RemoteExecution::Ssh.private_key_file
|
53
34
|
|
54
|
-
@
|
55
|
-
|
56
|
-
|
35
|
+
@session_args = { :logger => @logger,
|
36
|
+
:clock => @clock,
|
37
|
+
:connector_class => options[:connector_class] || Connector,
|
38
|
+
:local_working_dir => options[:local_working_dir] || '/tmp/foreman-proxy-ssh/server',
|
39
|
+
:remote_working_dir => options[:remote_working_dir] || '/tmp/foreman-proxy-ssh/client',
|
40
|
+
:client_private_key_file => Proxy::RemoteExecution::Ssh.private_key_file,
|
41
|
+
:refresh_interval => options[:refresh_interval] || 1 }
|
42
|
+
|
43
|
+
@sessions = {}
|
57
44
|
end
|
58
45
|
|
59
46
|
def initialize_command(command)
|
60
47
|
@logger.debug("initalizing command [#{command}]")
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
su_prefix = "su - #{command.effective_user} -c "
|
65
|
-
end
|
66
|
-
output_path = File.join(File.dirname(remote_script), 'output')
|
67
|
-
|
68
|
-
connector.async_run("#{su_prefix}#{remote_script} | /usr/bin/tee #{output_path}") do |data|
|
69
|
-
command_buffer(command) << data
|
70
|
-
end
|
71
|
-
rescue => e
|
72
|
-
@logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
|
73
|
-
command_buffer(command).concat([Connector::DebugData.new("Exception: #{e.class} #{e.message}"),
|
74
|
-
Connector::StatusData.new('INIT_ERROR')])
|
75
|
-
ensure
|
76
|
-
plan_next_refresh
|
77
|
-
end
|
78
|
-
|
79
|
-
def refresh
|
80
|
-
finished_commands = []
|
81
|
-
refresh_connectors
|
82
|
-
|
83
|
-
@command_buffer.each do |command, buffer|
|
84
|
-
unless buffer.empty?
|
85
|
-
status = refresh_command_buffer(command, buffer)
|
86
|
-
if status
|
87
|
-
finished_commands << command
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
finished_commands.each { |command| finish_command(command) }
|
93
|
-
close_inactive_connectors
|
94
|
-
ensure
|
95
|
-
@refresh_planned = false
|
96
|
-
plan_next_refresh
|
97
|
-
end
|
98
|
-
|
99
|
-
def refresh_command_buffer(command, buffer)
|
100
|
-
status = nil
|
101
|
-
@logger.debug("command #{command} got new output: #{buffer.inspect}")
|
102
|
-
buffer.delete_if do |data|
|
103
|
-
if data.is_a? Connector::StatusData
|
104
|
-
status = data.data
|
105
|
-
true
|
106
|
-
end
|
107
|
-
end
|
108
|
-
command.suspended_action << CommandUpdate.new(buffer, status)
|
109
|
-
clear_command(command)
|
110
|
-
if status
|
111
|
-
@logger.debug("command [#{command}] finished with status #{status}")
|
112
|
-
return status
|
113
|
-
end
|
48
|
+
open_session(command)
|
49
|
+
rescue => exception
|
50
|
+
handle_command_exception(command, exception)
|
114
51
|
end
|
115
52
|
|
116
53
|
def kill(command)
|
117
54
|
@logger.debug("killing command [#{command}]")
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
def connector_for_command(command, only_if_exists = false)
|
124
|
-
if connector = @connectors[[command.host, command.ssh_user]]
|
125
|
-
return connector
|
126
|
-
end
|
127
|
-
return nil if only_if_exists
|
128
|
-
@connectors[[command.host, command.ssh_user]] = open_connector(command)
|
129
|
-
end
|
130
|
-
|
131
|
-
def local_command_dir(command)
|
132
|
-
File.join(@local_working_dir, command.id)
|
133
|
-
end
|
134
|
-
|
135
|
-
def local_command_file(command, filename)
|
136
|
-
File.join(local_command_dir(command), filename)
|
137
|
-
end
|
138
|
-
|
139
|
-
def remote_command_dir(command)
|
140
|
-
File.join(@remote_working_dir, command.id)
|
141
|
-
end
|
142
|
-
|
143
|
-
def remote_command_file(command, filename)
|
144
|
-
File.join(remote_command_dir(command), filename)
|
145
|
-
end
|
146
|
-
|
147
|
-
def ensure_local_directory(path)
|
148
|
-
if File.exist?(path)
|
149
|
-
raise "#{path} expected to be a directory" unless File.directory?(path)
|
150
|
-
else
|
151
|
-
FileUtils.mkdir_p(path)
|
152
|
-
end
|
153
|
-
return path
|
154
|
-
end
|
155
|
-
|
156
|
-
def cp_script_to_remote(connector, command)
|
157
|
-
local_script_file = write_command_file_locally(command, 'script', command.script)
|
158
|
-
File.chmod(0777, local_script_file)
|
159
|
-
remote_script_file = remote_command_file(command, 'script')
|
160
|
-
connector.upload_file(local_script_file, remote_script_file)
|
161
|
-
return remote_script_file
|
162
|
-
end
|
163
|
-
|
164
|
-
def write_command_file_locally(command, filename, content)
|
165
|
-
path = local_command_file(command, filename)
|
166
|
-
ensure_local_directory(File.dirname(path))
|
167
|
-
File.write(path, content)
|
168
|
-
return path
|
169
|
-
end
|
170
|
-
|
171
|
-
def open_connector(command)
|
172
|
-
options = { :logger => @logger }
|
173
|
-
options[:known_hosts_file] = prepare_known_hosts(command)
|
174
|
-
options[:client_private_key_file] = @client_private_key_file
|
175
|
-
@connector_class.new(command.host, command.ssh_user, options)
|
176
|
-
end
|
177
|
-
|
178
|
-
def prepare_known_hosts(command)
|
179
|
-
path = local_command_file(command, 'known_hosts')
|
180
|
-
if command.host_public_key
|
181
|
-
write_command_file_locally(command, 'known_hosts', "#{command.host} #{command.host_public_key}")
|
182
|
-
end
|
183
|
-
return path
|
184
|
-
end
|
185
|
-
|
186
|
-
def close_inactive_connectors
|
187
|
-
@connectors.delete_if do |_, connector|
|
188
|
-
if connector.inactive?
|
189
|
-
connector.close
|
190
|
-
true
|
191
|
-
end
|
192
|
-
end
|
55
|
+
session = @sessions[command.id]
|
56
|
+
session.tell(:kill) if session
|
57
|
+
rescue => exception
|
58
|
+
handle_command_exception(command, exception, false)
|
193
59
|
end
|
194
60
|
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
begin
|
200
|
-
connector.refresh
|
201
|
-
rescue => e
|
202
|
-
@command_buffer.each do |command, buffer|
|
203
|
-
if connector_for_command(command, false)
|
204
|
-
buffer << Connector::DebugData.new("Exception: #{e.class} #{e.message}")
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
61
|
+
def finish_command(command)
|
62
|
+
close_session(command)
|
63
|
+
rescue => exception
|
64
|
+
handle_command_exception(command, exception)
|
209
65
|
end
|
210
66
|
|
211
|
-
|
212
|
-
@command_buffer[command]
|
213
|
-
end
|
67
|
+
private
|
214
68
|
|
215
|
-
def
|
216
|
-
@
|
69
|
+
def handle_command_exception(command, exception, fatal = true)
|
70
|
+
@logger.error("error while dispatching command #{command} to session:"\
|
71
|
+
"#{exception.class} #{exception.message}:\n #{exception.backtrace.join("\n")}")
|
72
|
+
command_data = CommandUpdate.encode_exception("Failed to dispatch the command", exception, fatal)
|
73
|
+
command.suspended_action << CommandUpdate.new(command_data)
|
74
|
+
close_session(command) if fatal
|
217
75
|
end
|
218
76
|
|
219
|
-
def
|
220
|
-
@
|
77
|
+
def open_session(command)
|
78
|
+
raise "Session already opened for command #{command}" if @sessions[command.id]
|
79
|
+
options = { :name => "proxy-ssh-session-#{command.host}-#{command.ssh_user}-#{command.id}",
|
80
|
+
:args => [@session_args.merge(:command => command)],
|
81
|
+
:supervise => true }
|
82
|
+
@sessions[command.id] = Proxy::RemoteExecution::Ssh::Session.spawn(options)
|
221
83
|
end
|
222
84
|
|
223
|
-
def
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
85
|
+
def close_session(command)
|
86
|
+
session = @sessions.delete(command.id)
|
87
|
+
return unless session
|
88
|
+
@logger.debug("closing session for command [#{command}], #{@sessions.size} session(s) left ")
|
89
|
+
session.tell([:start_termination, Concurrent.future])
|
229
90
|
end
|
230
91
|
end
|
231
92
|
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'smart_proxy_remote_execution_ssh/session'
|
2
|
+
|
3
|
+
module Proxy::RemoteExecution::Ssh
|
4
|
+
# Service that handles running external commands for Actions::Command
|
5
|
+
# Dynflow action. It runs just one (actor) thread for all the commands
|
6
|
+
# running in the system and updates the Dynflow actions periodically.
|
7
|
+
class Session < ::Dynflow::Actor
|
8
|
+
def initialize(options = {})
|
9
|
+
@clock = options[:clock] || Dynflow::Clock.spawn('proxy-dispatcher-clock')
|
10
|
+
@logger = options[:logger] || Logger.new($stderr)
|
11
|
+
@connector_class = options[:connector_class] || Connector
|
12
|
+
@local_working_dir = options[:local_working_dir] || '/tmp/foreman-proxy-ssh/server'
|
13
|
+
@remote_working_dir = options[:remote_working_dir] || '/tmp/foreman-proxy-ssh/client'
|
14
|
+
@refresh_interval = options[:refresh_interval] || 1
|
15
|
+
@client_private_key_file = Proxy::RemoteExecution::Ssh.private_key_file
|
16
|
+
@command = options[:command]
|
17
|
+
|
18
|
+
@command_buffer = []
|
19
|
+
@refresh_planned = false
|
20
|
+
|
21
|
+
reference.tell(:initialize_command)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize_command
|
25
|
+
@logger.debug("initalizing command [#{@command}]")
|
26
|
+
open_connector
|
27
|
+
remote_script = cp_script_to_remote
|
28
|
+
if @command.effective_user && @command.effective_user != @command.ssh_user
|
29
|
+
su_prefix = "su - #{@command.effective_user} -c "
|
30
|
+
end
|
31
|
+
output_path = File.join(File.dirname(remote_script), 'output')
|
32
|
+
|
33
|
+
@connector.async_run("#{su_prefix}#{remote_script} | /usr/bin/tee #{output_path}") do |data|
|
34
|
+
@command_buffer << data
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
@logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
|
38
|
+
@command_buffer.concat(CommandUpdate.encode_exception("Error initializing command #{@command}", e))
|
39
|
+
refresh
|
40
|
+
ensure
|
41
|
+
plan_next_refresh
|
42
|
+
end
|
43
|
+
|
44
|
+
def refresh
|
45
|
+
@connector.refresh if @connector
|
46
|
+
|
47
|
+
unless @command_buffer.empty?
|
48
|
+
status = refresh_command_buffer
|
49
|
+
if status
|
50
|
+
finish_command
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue => e
|
54
|
+
@command_buffer.concat(CommandUpdate.encode_exception("Failed to refresh the connector", e, false))
|
55
|
+
ensure
|
56
|
+
@refresh_planned = false
|
57
|
+
plan_next_refresh
|
58
|
+
end
|
59
|
+
|
60
|
+
def refresh_command_buffer
|
61
|
+
@logger.debug("command #{@command} got new output: #{@command_buffer.inspect}")
|
62
|
+
command_update = CommandUpdate.new(@command_buffer)
|
63
|
+
@command.suspended_action << command_update
|
64
|
+
@command_buffer = []
|
65
|
+
if command_update.exit_status
|
66
|
+
@logger.debug("command [#{@command}] finished with status #{command_update.exit_status}")
|
67
|
+
return command_update.exit_status
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def kill
|
72
|
+
@logger.debug("killing command [#{@command}]")
|
73
|
+
if @connector
|
74
|
+
@connector.run("pkill -f #{remote_command_file('script')}")
|
75
|
+
else
|
76
|
+
@logger.debug("connection closed")
|
77
|
+
end
|
78
|
+
rescue => e
|
79
|
+
@command_buffer.concat(CommandUpdate.encode_exception("Failed to kill the command", e, false))
|
80
|
+
plan_next_refresh
|
81
|
+
end
|
82
|
+
|
83
|
+
def finish_command
|
84
|
+
close
|
85
|
+
dispatcher.tell([:finish_command, @command])
|
86
|
+
end
|
87
|
+
|
88
|
+
def dispatcher
|
89
|
+
self.parent
|
90
|
+
end
|
91
|
+
|
92
|
+
def start_termination(*args)
|
93
|
+
super
|
94
|
+
close
|
95
|
+
finish_termination
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def open_connector
|
101
|
+
raise 'Connector already opened' if @connector
|
102
|
+
options = { :logger => @logger }
|
103
|
+
options[:known_hosts_file] = prepare_known_hosts
|
104
|
+
options[:client_private_key_file] = @client_private_key_file
|
105
|
+
@connector = @connector_class.new(@command.host, @command.ssh_user, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def local_command_dir
|
109
|
+
File.join(@local_working_dir, @command.id)
|
110
|
+
end
|
111
|
+
|
112
|
+
def local_command_file(filename)
|
113
|
+
File.join(local_command_dir, filename)
|
114
|
+
end
|
115
|
+
|
116
|
+
def remote_command_dir
|
117
|
+
File.join(@remote_working_dir, @command.id)
|
118
|
+
end
|
119
|
+
|
120
|
+
def remote_command_file(filename)
|
121
|
+
File.join(remote_command_dir, filename)
|
122
|
+
end
|
123
|
+
|
124
|
+
def ensure_local_directory(path)
|
125
|
+
if File.exist?(path)
|
126
|
+
raise "#{path} expected to be a directory" unless File.directory?(path)
|
127
|
+
else
|
128
|
+
FileUtils.mkdir_p(path)
|
129
|
+
end
|
130
|
+
return path
|
131
|
+
end
|
132
|
+
|
133
|
+
def cp_script_to_remote
|
134
|
+
local_script_file = write_command_file_locally('script', @command.script)
|
135
|
+
File.chmod(0777, local_script_file)
|
136
|
+
remote_script_file = remote_command_file('script')
|
137
|
+
@connector.upload_file(local_script_file, remote_script_file)
|
138
|
+
return remote_script_file
|
139
|
+
end
|
140
|
+
|
141
|
+
def write_command_file_locally(filename, content)
|
142
|
+
path = local_command_file(filename)
|
143
|
+
ensure_local_directory(File.dirname(path))
|
144
|
+
File.write(path, content)
|
145
|
+
return path
|
146
|
+
end
|
147
|
+
|
148
|
+
def prepare_known_hosts
|
149
|
+
path = local_command_file('known_hosts')
|
150
|
+
if @command.host_public_key
|
151
|
+
write_command_file_locally('known_hosts', "#{@command.host} #{@command.host_public_key}")
|
152
|
+
end
|
153
|
+
return path
|
154
|
+
end
|
155
|
+
|
156
|
+
def close
|
157
|
+
@connector.close if @connector
|
158
|
+
@connector = nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def plan_next_refresh
|
162
|
+
if @connector && !@refresh_planned
|
163
|
+
@logger.debug("planning to refresh")
|
164
|
+
@clock.ping(reference, Time.now + @refresh_interval, :refresh)
|
165
|
+
@refresh_planned = true
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -3,6 +3,8 @@ require 'smart_proxy_dynflow'
|
|
3
3
|
require 'smart_proxy_remote_execution_ssh/version'
|
4
4
|
require 'smart_proxy_remote_execution_ssh/plugin'
|
5
5
|
|
6
|
+
require 'smart_proxy_remote_execution_ssh/connector'
|
7
|
+
require 'smart_proxy_remote_execution_ssh/command_update'
|
6
8
|
require 'smart_proxy_remote_execution_ssh/dispatcher'
|
7
9
|
require 'smart_proxy_remote_execution_ssh/command_action'
|
8
10
|
|
metadata
CHANGED
@@ -1,97 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_remote_execution_ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
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: 2015-08-
|
11
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.7'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ! '>='
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ! '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: mocha
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '1'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: webmock
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ~>
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '1'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rack-test
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ~>
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ~>
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
@@ -112,46 +112,47 @@ dependencies:
|
|
112
112
|
name: smart_proxy_dynflow
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: 0.0.3
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - ~>
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 0.0.3
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: net-ssh
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ! '>='
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ! '>='
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: net-scp
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- -
|
143
|
+
- - ! '>='
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: '0'
|
146
146
|
type: :runtime
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- -
|
150
|
+
- - ! '>='
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
|
-
description:
|
154
|
-
|
153
|
+
description: ! ' Ssh remote execution provider for Foreman Smart-Proxy
|
154
|
+
|
155
|
+
'
|
155
156
|
email:
|
156
157
|
- inecas@redhat.com
|
157
158
|
executables: []
|
@@ -162,14 +163,17 @@ extra_rdoc_files:
|
|
162
163
|
files:
|
163
164
|
- LICENSE
|
164
165
|
- README.md
|
166
|
+
- bundler.d/Gemfile.local.rb
|
165
167
|
- bundler.d/remote_execution_ssh.rb
|
166
168
|
- lib/smart_proxy_remote_execution_ssh.rb
|
167
169
|
- lib/smart_proxy_remote_execution_ssh/api.rb
|
168
170
|
- lib/smart_proxy_remote_execution_ssh/command_action.rb
|
171
|
+
- lib/smart_proxy_remote_execution_ssh/command_update.rb
|
169
172
|
- lib/smart_proxy_remote_execution_ssh/connector.rb
|
170
173
|
- lib/smart_proxy_remote_execution_ssh/dispatcher.rb
|
171
174
|
- lib/smart_proxy_remote_execution_ssh/http_config.ru
|
172
175
|
- lib/smart_proxy_remote_execution_ssh/plugin.rb
|
176
|
+
- lib/smart_proxy_remote_execution_ssh/session.rb
|
173
177
|
- lib/smart_proxy_remote_execution_ssh/version.rb
|
174
178
|
- settings.d/remote_execution_ssh.yml.example
|
175
179
|
homepage: https://github.com/theforeman/smart_proxy_remote_execution_ssh
|
@@ -182,18 +186,19 @@ require_paths:
|
|
182
186
|
- lib
|
183
187
|
required_ruby_version: !ruby/object:Gem::Requirement
|
184
188
|
requirements:
|
185
|
-
- -
|
189
|
+
- - ! '>='
|
186
190
|
- !ruby/object:Gem::Version
|
187
191
|
version: '0'
|
188
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
193
|
requirements:
|
190
|
-
- -
|
194
|
+
- - ! '>='
|
191
195
|
- !ruby/object:Gem::Version
|
192
196
|
version: '0'
|
193
197
|
requirements: []
|
194
198
|
rubyforge_project:
|
195
|
-
rubygems_version: 2.4.
|
199
|
+
rubygems_version: 2.4.8
|
196
200
|
signing_key:
|
197
201
|
specification_version: 4
|
198
202
|
summary: Ssh remote execution provider for Foreman Smart-Proxy
|
199
203
|
test_files: []
|
204
|
+
has_rdoc:
|