smart_proxy_remote_execution_ssh 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|