remote-session 0.0.4 → 0.0.5
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.
- data/lib/remote/session.rb +46 -14
- data/lib/remote/session/version.rb +1 -1
- data/spec/unit/session_spec.rb +139 -135
- metadata +4 -4
data/lib/remote/session.rb
CHANGED
@@ -8,7 +8,9 @@ require 'base64'
|
|
8
8
|
|
9
9
|
module Remote
|
10
10
|
class Session
|
11
|
-
|
11
|
+
SUDO_PASSWORD_PROMPT = 'remote-session-sudo-prompt'
|
12
|
+
ROOT_COMMAND_PROMPT = 'remote-session-prompt#'
|
13
|
+
ROOT_COMMAND_PROMPT_MATCH = /#{ ROOT_COMMAND_PROMPT }$/
|
12
14
|
|
13
15
|
def self.open( host, options = {}, &block )
|
14
16
|
rs = new( host, options )
|
@@ -47,7 +49,7 @@ module Remote
|
|
47
49
|
|
48
50
|
def sudo( commands )
|
49
51
|
raise "Session is closed" if @session.nil?
|
50
|
-
commands = [ *commands ]
|
52
|
+
commands = [ *commands ]
|
51
53
|
|
52
54
|
@session.open_channel do |ch|
|
53
55
|
ch.request_pty do |ch, success|
|
@@ -89,10 +91,10 @@ module Remote
|
|
89
91
|
def channel_exec( ch, commands )
|
90
92
|
ch[ :commands ] = commands
|
91
93
|
|
92
|
-
ch.exec "sudo -k -p '#{
|
94
|
+
ch.exec "sudo -k -p '#{ SUDO_PASSWORD_PROMPT }' su -" do |ch, success|
|
93
95
|
raise "Could not execute sudo su command" if ! success
|
94
96
|
|
95
|
-
ch.on_data
|
97
|
+
ch.on_data &method( :handle_sudo_password_prompt )
|
96
98
|
|
97
99
|
ch.on_extended_data do |ch, type, data|
|
98
100
|
$stderr.puts data
|
@@ -100,11 +102,35 @@ module Remote
|
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
103
|
-
def
|
104
|
-
|
105
|
+
def handle_sudo_password_prompt( ch, data )
|
106
|
+
$stdout.write data
|
107
|
+
|
108
|
+
if data =~ Regexp.new( SUDO_PASSWORD_PROMPT )
|
105
109
|
ch.send_data "#{ @sudo_password }\n"
|
106
|
-
|
110
|
+
ch.on_data &method( :set_command_prompt )
|
107
111
|
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the root command prompt to something we can
|
115
|
+
# recognise, and wait until that prompt comes back
|
116
|
+
def set_command_prompt( ch, data )
|
117
|
+
$stdout.write data
|
118
|
+
|
119
|
+
if data =~ ROOT_COMMAND_PROMPT_MATCH
|
120
|
+
# Got it, now we can switch so sending commands
|
121
|
+
ch[ :awaiting_prompt ] = false
|
122
|
+
ch.on_data &method( :on_data )
|
123
|
+
do_command ch, data
|
124
|
+
elsif ! ch[ :awaiting_prompt ]
|
125
|
+
# this is the first time through...
|
126
|
+
ch[ :awaiting_prompt ] = true
|
127
|
+
ch.send_data "export PS1='#{ ROOT_COMMAND_PROMPT }'"
|
128
|
+
# else: Waiting for new root prompt
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_data( ch, data )
|
133
|
+
$stdout.write data
|
108
134
|
|
109
135
|
@prompts.each_pair do | prompt, send |
|
110
136
|
if data =~ Regexp.new( prompt )
|
@@ -113,15 +139,21 @@ module Remote
|
|
113
139
|
end
|
114
140
|
end
|
115
141
|
|
116
|
-
|
117
|
-
|
118
|
-
|
142
|
+
if data =~ ROOT_COMMAND_PROMPT_MATCH
|
143
|
+
do_command ch, data
|
144
|
+
end
|
145
|
+
end
|
119
146
|
|
120
|
-
|
121
|
-
if
|
122
|
-
|
147
|
+
def do_command( ch, data )
|
148
|
+
if ch[ :commands ].size > 0
|
149
|
+
command = ch[ :commands ].shift
|
150
|
+
if command.is_a?( Remote::Session::Send )
|
151
|
+
send_file_chunk( ch, command )
|
152
|
+
else
|
153
|
+
ch.send_data "#{command}\n"
|
154
|
+
end
|
123
155
|
else
|
124
|
-
ch.send_data "
|
156
|
+
ch.send_data "exit\n"
|
125
157
|
end
|
126
158
|
end
|
127
159
|
|
data/spec/unit/session_spec.rb
CHANGED
@@ -6,19 +6,28 @@ module SpecOutputCapture
|
|
6
6
|
def expect_output
|
7
7
|
stdout = []
|
8
8
|
@rs.stub!( :puts ) do | s |
|
9
|
-
stdout <<
|
9
|
+
stdout << s + "\n"
|
10
|
+
end
|
11
|
+
$stdout.stub!( :puts ) do | s |
|
12
|
+
stdout << s + "\n"
|
10
13
|
end
|
11
14
|
$stdout.stub!( :write ) do | s |
|
12
15
|
stdout << s
|
13
16
|
end
|
14
17
|
stderr = []
|
18
|
+
$stderr.stub!( :puts ) do | s |
|
19
|
+
stderr << s + "\n"
|
20
|
+
end
|
15
21
|
$stderr.stub!( :write ) do | s |
|
16
22
|
stderr << s
|
17
23
|
end
|
18
24
|
|
19
25
|
yield
|
20
26
|
|
21
|
-
|
27
|
+
$stdout.rspec_reset
|
28
|
+
$stderr.rspec_reset
|
29
|
+
|
30
|
+
{ :stdout => stdout, :stderr => stderr }
|
22
31
|
end
|
23
32
|
|
24
33
|
end
|
@@ -179,7 +188,7 @@ describe Remote::Session do
|
|
179
188
|
request_pty_block.call( @ch, true )
|
180
189
|
end
|
181
190
|
|
182
|
-
@ch.should_receive( :exec ).with( "sudo -k -p '
|
191
|
+
@ch.should_receive( :exec ).with( "sudo -k -p 'remote-session-sudo-prompt' su -" )
|
183
192
|
|
184
193
|
open_channel_block.call @ch
|
185
194
|
end
|
@@ -234,187 +243,182 @@ describe Remote::Session do
|
|
234
243
|
@ch.stub!( :exec ) do |&block|
|
235
244
|
block.call( @ch, true )
|
236
245
|
end
|
246
|
+
@ch.stub!( :send_data => nil )
|
237
247
|
@ch.stub!( :on_extended_data => nil )
|
238
|
-
|
239
|
-
$stdout.stub!( :write => nil )
|
240
|
-
end
|
248
|
+
$stdout.stub( :write => nil )
|
241
249
|
|
242
|
-
|
250
|
+
# For each call to Channel.on_data, @@data contains the strings
|
251
|
+
# to send back to the block
|
252
|
+
@@data = [ [ 'remote-session-sudo-prompt' ], # channel_exec call
|
253
|
+
[ 'any prompt#', 'remote-session-prompt#' ], # handle_sudo_password_prompt call
|
254
|
+
[ 'remote-session-prompt#', 'remote-session-prompt#', 'remote-session-prompt#' ] ]
|
243
255
|
@ch.stub!( :on_data ) do |&block|
|
244
|
-
block.call
|
256
|
+
@@data.shift.each { |response| block.call @ch, response }
|
245
257
|
end
|
258
|
+
end
|
246
259
|
|
247
|
-
|
260
|
+
it 'should supply the sudo password, when prompted' do
|
261
|
+
@ch.should_receive( :send_data ).with( "secret\n" )
|
248
262
|
|
249
|
-
|
263
|
+
@rs.sudo( 'pwd' )
|
250
264
|
end
|
251
265
|
|
252
|
-
it 'should
|
253
|
-
|
254
|
-
|
255
|
-
block.call( @ch, 'the_prompt' )
|
256
|
-
block.call( @ch, 'the_prompt' )
|
257
|
-
block.call( @ch, 'the_prompt' )
|
266
|
+
it 'should echo the sudo prompt' do
|
267
|
+
output = expect_output do
|
268
|
+
@rs.sudo( 'pwd' )
|
258
269
|
end
|
270
|
+
|
271
|
+
output[ :stdout ][ 0 ].should == 'remote-session-sudo-prompt'
|
272
|
+
end
|
259
273
|
|
260
|
-
|
261
|
-
@ch.stub!( :send_data ) { | s | sent << s }
|
262
|
-
|
263
|
-
subject.sudo( [ 'pwd', 'cd /etc', 'ls' ] )
|
274
|
+
context 'after sudo prompt' do
|
264
275
|
|
265
|
-
|
266
|
-
|
276
|
+
it 'should set the root command prompt' do
|
277
|
+
@ch.should_receive( :send_data ).with( "export PS1='remote-session-prompt#'" )
|
267
278
|
|
268
|
-
|
269
|
-
@ch.stub!( :on_data ) do |&block|
|
270
|
-
block.call( @ch, 'some_data' )
|
279
|
+
@rs.sudo( 'pwd' )
|
271
280
|
end
|
272
281
|
|
273
|
-
|
274
|
-
|
275
|
-
end.should == [ [ 'some_data' ], [] ]
|
276
|
-
end
|
282
|
+
it 'should echo until the prompt is set' do
|
283
|
+
@@data[ 1 ] = [ 'prompt#', 'stuff1', 'stuff2', 'remote-session-prompt#' ]
|
277
284
|
|
278
|
-
|
285
|
+
output = expect_output do
|
286
|
+
@rs.sudo( 'pwd' )
|
287
|
+
end
|
279
288
|
|
280
|
-
|
281
|
-
|
289
|
+
output[ :stdout ].should include 'stuff1'
|
290
|
+
output[ :stdout ].should include 'stuff2'
|
282
291
|
end
|
283
292
|
|
284
|
-
|
285
|
-
@ch.stub!( :on_data ) do |&block|
|
286
|
-
block.call( @ch, 'root_prompt#' )
|
287
|
-
block.call( @ch, 'root_prompt#' )
|
288
|
-
block.call( @ch, 'root_prompt#' )
|
289
|
-
end
|
293
|
+
context 'with special command prompt' do
|
290
294
|
|
291
|
-
|
292
|
-
|
295
|
+
it 'should send the command' do
|
296
|
+
@ch.should_receive( :send_data ).with( "pwd\n" )
|
293
297
|
|
294
|
-
|
295
|
-
open = false
|
296
|
-
@sf.should_receive( :open ) do
|
297
|
-
chunk = 1
|
298
|
-
open = true
|
298
|
+
subject.sudo( 'pwd' )
|
299
299
|
end
|
300
300
|
|
301
|
-
|
302
|
-
|
301
|
+
it 'should run multiple commands' do
|
302
|
+
sent = []
|
303
|
+
@ch.stub!( :send_data ) { | s | sent << s }
|
304
|
+
|
305
|
+
subject.sudo( [ 'pwd', 'cd /etc', 'ls' ] )
|
306
|
+
|
307
|
+
sent[-4..-1].should == [ "pwd\n", "cd /etc\n", "ls\n", "exit\n" ]
|
303
308
|
end
|
304
309
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
when 1, 2
|
310
|
-
false
|
311
|
-
else
|
312
|
-
true
|
310
|
+
it 'should output returning data' do
|
311
|
+
@@data[ 2 ] = [ 'remote-session-prompt#', 'some_data', 'remote-session-prompt#' ]
|
312
|
+
output = expect_output do
|
313
|
+
@rs.sudo( 'pwd' )
|
313
314
|
end
|
314
|
-
end
|
315
315
|
|
316
|
-
|
317
|
-
@sf.should_receive( :read ).twice do
|
318
|
-
d = data[ chunk ]
|
319
|
-
chunk += 1
|
320
|
-
d
|
316
|
+
output[ :stdout ].should include 'some_data'
|
321
317
|
end
|
322
318
|
|
323
|
-
|
324
|
-
@ch.stub!( :send_data ) { | d | sent << d }
|
325
|
-
|
326
|
-
@rs.sudo( @sf )
|
319
|
+
context 'sending files' do
|
327
320
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
"exit\n"
|
332
|
-
]
|
333
|
-
end
|
321
|
+
before :each do
|
322
|
+
@sf = stub( 'Remote::Session::SendFile instance', :open => nil, :close => nil )
|
323
|
+
end
|
334
324
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
325
|
+
it 'should copy files' do
|
326
|
+
@sf.should_receive( :is_a? ).with( Remote::Session::Send ).twice.and_return( true )
|
327
|
+
@sf.should_receive( :remote_path ).twice.and_return( '/remote/path' )
|
328
|
+
|
329
|
+
chunk = 0
|
330
|
+
open = false
|
331
|
+
@sf.should_receive( :open ) do
|
332
|
+
chunk = 1
|
333
|
+
open = true
|
334
|
+
end
|
335
|
+
|
336
|
+
@sf.stub!( :open? ) do
|
337
|
+
open
|
338
|
+
end
|
339
|
+
|
340
|
+
@sf.stub!( :eof? ) do
|
341
|
+
case chunk
|
342
|
+
when 0
|
343
|
+
true
|
344
|
+
when 1, 2
|
345
|
+
false
|
346
|
+
else
|
347
|
+
true
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
data = [ nil, 'first_chunk', 'second_chunk' ]
|
352
|
+
@sf.should_receive( :read ).twice do
|
353
|
+
d = data[ chunk ]
|
354
|
+
chunk += 1
|
355
|
+
d
|
356
|
+
end
|
357
|
+
|
358
|
+
sent = []
|
359
|
+
@ch.stub!( :send_data ) { | d | sent << d }
|
360
|
+
|
361
|
+
@rs.sudo( @sf )
|
362
|
+
|
363
|
+
sent.should include "echo -n 'Zmlyc3RfY2h1bms=\n' | base64 -d > /remote/path\n"
|
364
|
+
sent.should include "echo -n 'c2Vjb25kX2NodW5r\n' | base64 -d >> /remote/path\n"
|
365
|
+
end
|
340
366
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
367
|
+
it 'should copy empty files' do
|
368
|
+
@sf.stub!( :is_a? ).with( Remote::Session::Send ).and_return( true )
|
369
|
+
@sf.stub!( :open? => false )
|
370
|
+
@sf.stub!( :remote_path ).and_return( '/remote/path' )
|
371
|
+
@sf.stub!( :eof? => true )
|
345
372
|
|
346
|
-
|
347
|
-
|
373
|
+
sent = []
|
374
|
+
@ch.stub!( :send_data ) { | d | sent << d }
|
348
375
|
|
349
376
|
|
350
|
-
|
377
|
+
@rs.sudo( @sf )
|
351
378
|
|
352
|
-
|
353
|
-
|
354
|
-
"exit\n"
|
355
|
-
]
|
356
|
-
end
|
357
|
-
|
358
|
-
end
|
379
|
+
sent.should include "echo -n '' | base64 -d > /remote/path\n"
|
380
|
+
end
|
359
381
|
|
360
|
-
context 'with password prompt' do
|
361
|
-
before :each do
|
362
|
-
$stdout.stub( :write )
|
363
|
-
@ch.stub!( :on_data ) do |&block|
|
364
|
-
block.call( @ch, 'sudo_prompt' )
|
365
|
-
block.call( @ch, 'root#' )
|
366
382
|
end
|
367
|
-
end
|
368
|
-
|
369
|
-
it 'should supply the sudo password, when prompted' do
|
370
|
-
@ch.should_receive( :send_data ).with( "secret\n" )
|
371
383
|
|
372
|
-
|
373
|
-
|
384
|
+
context 'with user-supplied prompt' do
|
385
|
+
it 'should send the supplied data' do
|
386
|
+
@@data[ 2 ] = [ 'remote-session-prompt#', 'Supply user password:', 'remote-session-prompt#' ]
|
387
|
+
@ch.should_receive( :send_data ).with( "this data\n" )
|
388
|
+
@rs.prompts[ 'user password:' ] = 'this data'
|
374
389
|
|
375
|
-
|
376
|
-
|
377
|
-
@rs.sudo( 'pwd' )
|
378
|
-
end.should == [ [ 'root#' ], [] ]
|
379
|
-
end
|
380
|
-
end
|
390
|
+
@rs.sudo( 'pwd' )
|
391
|
+
end
|
381
392
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
@ch.stub!( :on_data ) do |&block|
|
386
|
-
block.call( @ch, 'Here is my prompt:' )
|
387
|
-
block.call( @ch, 'root#' )
|
388
|
-
end
|
389
|
-
end
|
393
|
+
it 'should echo the prompt' do
|
394
|
+
@@data[ 2 ] = [ 'remote-session-prompt#', 'Supply user password:', 'remote-session-prompt#' ]
|
395
|
+
@rs.prompts[ 'user password:' ] = 'this data'
|
390
396
|
|
391
|
-
|
392
|
-
|
393
|
-
|
397
|
+
output = expect_output do
|
398
|
+
@rs.sudo( 'pwd' )
|
399
|
+
end
|
400
|
+
|
401
|
+
output[ :stdout ].should include 'Supply user password:'
|
402
|
+
end
|
394
403
|
|
395
|
-
|
396
|
-
end
|
404
|
+
end
|
397
405
|
|
398
|
-
|
399
|
-
|
406
|
+
it 'should send error data to stdout' do
|
407
|
+
@ch.stub!( :on_data )
|
400
408
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
end
|
409
|
+
@ch.stub!( :on_extended_data ) do |&block|
|
410
|
+
block.call @ch, 'foo', 'It failed'
|
411
|
+
end
|
405
412
|
|
406
|
-
|
413
|
+
output = expect_output do
|
414
|
+
@rs.sudo( 'pwd' )
|
415
|
+
end
|
407
416
|
|
408
|
-
|
409
|
-
|
417
|
+
output[ :stderr ].should include "It failed\n"
|
418
|
+
end
|
410
419
|
|
411
|
-
@ch.stub!( :on_extended_data ) do |&block|
|
412
|
-
block.call @ch, 'foo', 'It failed'
|
413
420
|
end
|
414
421
|
|
415
|
-
expect_output do
|
416
|
-
@rs.sudo( 'pwd' )
|
417
|
-
end.should == [ [], [ "It failed", "\n" ] ]
|
418
422
|
end
|
419
423
|
|
420
424
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: remote-session
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Joe Yates
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-06-03 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
prerelease: false
|