remote-session 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .rvmrc
3
+ Gemfile.lock
4
+ coverage
5
+ pkg
6
+
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ script: "bundle exec rake spec"
6
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Joe Yates
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ remote-session [![Build Status](https://secure.travis-ci.org/joeyates/remote-session.png)][Continuous Integration]
2
+ ==============
3
+
4
+ *Run user, and sudo, commands over an SSH connection*
5
+
6
+ * [Source Code]
7
+ * [API documentation]
8
+ * [Rubygem]
9
+ * [Continuous Integration]
10
+
11
+ [Source Code]: https://github.com/joeyates/remote-session "Source code at GitHub"
12
+ [API documentation]: http://rubydoc.info/gems/remote-session/frames "RDoc API Documentation at Rubydoc.info"
13
+ [Rubygem]: http://rubygems.org/gems/remote-session "Ruby gem at rubygems.org"
14
+ [Continuous Integration]: http://travis-ci.org/joeyates/remote-session "Build status by Travis-CI"
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ gem 'remote-session'
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install remote-session
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+ require 'remote-session'
34
+
35
+ r = Remote::Session.new( 'host.example.com' )
36
+ puts r.run( 'pwd' )
37
+ puts r.sudo( 'apt-get update' )
38
+ r.close
39
+ ```
40
+
41
+ In a block:
42
+ ```ruby
43
+ Remote::Session.new( 'host.example.com', :user => 'user' ) | r | do
44
+ puts r.run( 'pwd' )
45
+ puts r.sudo( 'apt-get update' )
46
+ end
47
+ ```
48
+
49
+ Options:
50
+ ```ruby
51
+ Remote::Session.new( 'host.example.com', :user => 'user', :password => 'password' ) | r | do
52
+ puts r.run( 'pwd' )
53
+ puts r.sudo( 'apt-get update' )
54
+ end
55
+ ```
56
+
57
+ ## Contributing
58
+
59
+ 1. Fork it
60
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
61
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
62
+ 4. Push to the branch (`git push origin my-new-feature`)
63
+ 5. Create new Pull Request
64
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default => :spec
6
+
7
+ RSpec::Core::RakeTask.new do | t |
8
+ t.pattern = 'spec/**/*_spec.rb'
9
+ end
10
+
11
+ if RUBY_VERSION < '1.9'
12
+
13
+ RSpec::Core::RakeTask.new( 'spec:coverage' ) do |t|
14
+ t.pattern = 'spec/**/*_spec.rb'
15
+ t.rcov = true
16
+ t.rcov_opts = [ '--exclude', 'spec/,/gems/' ]
17
+ end
18
+
19
+ else
20
+
21
+ desc 'Run specs and create coverage output'
22
+ RSpec::Core::RakeTask.new( 'spec:coverage' ) do |t|
23
+ t.pattern = ['spec/gather_rspec_coverage.rb', 'spec/**/*_spec.rb']
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,90 @@
1
+ require 'remote/session/version'
2
+ require 'net/ssh'
3
+
4
+ module Remote
5
+ class Session
6
+ SUDO_PROMPT = 'sudo_prompt'
7
+
8
+ def self.open( host, options = {}, &block )
9
+ rs = new( host, options )
10
+
11
+ block.call rs
12
+
13
+ rs.close
14
+ end
15
+
16
+ attr_accessor :host, :user, :password, :options
17
+
18
+ def initialize( host, options = {} )
19
+ @host = host
20
+ @options = options.clone
21
+ @user = @options.delete( :user ) || ENV[ 'USER' ]
22
+ @password = @options.delete( :password )
23
+ connect
24
+ end
25
+
26
+ def run( command )
27
+ raise "Session is closed" if @session.nil?
28
+ puts "@#{ @host }: #{ command }"
29
+ puts @session.exec!( command )
30
+ end
31
+
32
+ def sudo( command, prompts = {} )
33
+ raise "Session is closed" if @session.nil?
34
+
35
+ puts "@#{ @host }: sudo #{ command }"
36
+ @session.open_channel do |ch|
37
+ ch.request_pty do |ch, success|
38
+ raise "Could not obtain pty" if ! success
39
+
40
+ channel_exec ch, command, prompts
41
+ end
42
+ end
43
+ @session.loop
44
+ end
45
+
46
+ def close
47
+ @session.close
48
+ @session = nil
49
+ end
50
+
51
+ private
52
+
53
+ def ssh_options
54
+ s = {}
55
+ s[ :password ] = @password if @password
56
+ s
57
+ end
58
+
59
+ def connect
60
+ @session = Net::SSH.start( @host, @user, ssh_options )
61
+ end
62
+
63
+ def channel_exec( ch, command, prompts )
64
+ ch.exec "sudo -p '#{ SUDO_PROMPT }' #{ command }" do |ch, success|
65
+ raise "Could not execute sudo command: #{ command }" if ! success
66
+
67
+ ch.on_data do | ch, data |
68
+ if data =~ Regexp.new( SUDO_PROMPT )
69
+ ch.send_data "#{ @options[ :sudo_password ] }\n"
70
+ else
71
+ prompt_matched = false
72
+ prompts.each_pair do | prompt, send |
73
+ if data =~ Regexp.new( prompt )
74
+ ch.send_data "#{ send }\n"
75
+ prompt_matched = true
76
+ end
77
+ end
78
+ puts data if ! prompt_matched
79
+ end
80
+ end
81
+
82
+ ch.on_extended_data do |ch, type, data|
83
+ raise "Error #{ data } while performing command: #{ command }"
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+
@@ -0,0 +1,5 @@
1
+ module Remote
2
+ class Session
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift( File.join( File.dirname( __FILE__ ), 'lib' ) )
3
+ require 'remote/session/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ['Joe Yates']
7
+ gem.email = ['joe.g.yates@gmail.com']
8
+ gem.description = %q{This gem uses Net::SSH to create a connection and allow command execution over it.
9
+ Run commands as the logged on user, or via sudo as any permitetd user (defaults to root).}
10
+ gem.summary = %q{Run user commands, and sudo, commands over an SSH connection}
11
+ gem.homepage = ''
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = 'remote-session'
16
+ gem.require_paths = ['lib']
17
+ gem.version = Remote::Session::VERSION
18
+
19
+ gem.add_runtime_dependency 'rake', '>= 0.8.7'
20
+ gem.add_runtime_dependency 'net-ssh'
21
+
22
+ gem.add_development_dependency 'rspec', '>= 2.3.0'
23
+ if RUBY_VERSION < '1.9'
24
+ gem.add_development_dependency 'rcov'
25
+ else
26
+ gem.add_development_dependency 'simplecov'
27
+ end
28
+
29
+ gem.rubyforge_project = 'nowarning'
30
+ end
31
+
@@ -0,0 +1,2 @@
1
+ GATHER_RSPEC_COVERAGE = 1
2
+
@@ -0,0 +1,15 @@
1
+ require 'rspec'
2
+
3
+ if RUBY_VERSION < '1.9'
4
+ require 'rspec/autorun'
5
+ else
6
+ require 'simplecov'
7
+ if defined?( GATHER_RSPEC_COVERAGE )
8
+ SimpleCov.start do
9
+ add_filter "/spec/"
10
+ end
11
+ end
12
+ end
13
+
14
+ require File.expand_path( File.dirname(__FILE__) + '/../lib/remote/session' )
15
+
@@ -0,0 +1,301 @@
1
+ # encoding: utf-8
2
+ load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
3
+
4
+ describe Remote::Session do
5
+
6
+ TEST_HOST = 'host.example.com'
7
+
8
+ context 'initialization' do
9
+
10
+ before :each do
11
+ Net::SSH.stub!( :start )
12
+ @user = ENV[ 'USER' ]
13
+ ENV[ 'USER' ] = 'the_user'
14
+ end
15
+
16
+ after :each do
17
+ ENV[ 'USER' ] = @user
18
+ end
19
+
20
+ it 'should require a hostname parameter' do
21
+ expect do
22
+ Remote::Session.new
23
+ end. to raise_error( ArgumentError, 'wrong number of arguments (0 for 1)' )
24
+ end
25
+
26
+ it 'should connect automatically' do
27
+ Net::SSH.should_receive( :start )
28
+
29
+ Remote::Session.new( TEST_HOST )
30
+ end
31
+
32
+ it 'user should default to the current user' do
33
+ Net::SSH.should_receive( :start ).with( TEST_HOST, 'the_user', {} )
34
+
35
+ Remote::Session.new( TEST_HOST )
36
+ end
37
+
38
+ it 'should accept user option' do
39
+ Net::SSH.should_receive( :start ).with( TEST_HOST, 'another_user', {} )
40
+
41
+ Remote::Session.new( TEST_HOST, :user => 'another_user' )
42
+ end
43
+
44
+ it 'should use any supplied password' do
45
+ Net::SSH.should_receive( :start ).with( TEST_HOST, 'another_user', { :password => 'secret' } )
46
+
47
+ Remote::Session.new( TEST_HOST, :user => 'another_user', :password => 'secret' )
48
+ end
49
+
50
+ end
51
+
52
+ context 'instance methods' do
53
+
54
+ before :each do
55
+ @ssh = stub( 'Net::SSH instance' )
56
+ Net::SSH.stub!( :start => @ssh )
57
+ end
58
+
59
+ context '#open' do
60
+
61
+ it 'should run the block' do
62
+ @ssh.stub!( :close )
63
+
64
+ called = false
65
+ Remote::Session.open( TEST_HOST, :user => 'another_user' ) do | rs |
66
+ called = true
67
+ end
68
+
69
+ called.should be_true
70
+ end
71
+
72
+ it 'should open a connection' do
73
+ @ssh.stub!( :close )
74
+
75
+ Net::SSH.should_receive( :start ).with( 'host.example.com', 'another_user', {} )
76
+
77
+ Remote::Session.open( TEST_HOST, :user => 'another_user' ) {}
78
+ end
79
+
80
+ it 'should require a block' do
81
+ expect do
82
+ Remote::Session.open( TEST_HOST, :user => 'another_user' )
83
+ end. to raise_error( NoMethodError, "undefined method `call' for nil:NilClass" )
84
+ end
85
+
86
+ it 'should close the connection' do
87
+ @ssh.should_receive( :close )
88
+
89
+ Remote::Session.open( TEST_HOST, :user => 'another_user' ) {}
90
+ end
91
+
92
+ end
93
+
94
+ context '#run' do
95
+
96
+ subject { Remote::Session.new( TEST_HOST ) }
97
+
98
+ it 'should fail, if the session is closed' do
99
+ @ssh.stub!( :close )
100
+ subject.close
101
+
102
+ expect do
103
+ subject.run( 'pwd' )
104
+ end.to raise_error( RuntimeError, 'Session is closed' )
105
+ end
106
+
107
+ it 'should print the command to stdout' do
108
+ @ssh.stub!( :exec! => "/foo/bar\n" )
109
+
110
+ subject.should_receive( :puts ).with( "@#{TEST_HOST}: pwd" )
111
+ subject.should_receive( :puts ).with( "/foo/bar\n" )
112
+
113
+ subject.run( 'pwd' )
114
+ end
115
+
116
+ it 'should run the command' do
117
+ subject.stub!( :puts )
118
+
119
+ @ssh.should_receive( :exec! ).with( 'pwd' ).and_return( "/foo/bar\n" )
120
+
121
+ subject.run( 'pwd' )
122
+ end
123
+
124
+ end
125
+
126
+ context '#sudo' do
127
+
128
+ subject { Remote::Session.new( TEST_HOST ) }
129
+
130
+ it 'should fail, if the session is closed' do
131
+ @ssh.stub!( :close )
132
+ subject.close
133
+
134
+ expect do
135
+ subject.sudo( 'pwd' )
136
+ end.to raise_error( RuntimeError, 'Session is closed' )
137
+ end
138
+
139
+ it 'should print the command to stdout' do
140
+ @ssh.stub!( :open_channel => nil )
141
+ @ssh.stub!( :loop => nil )
142
+
143
+ subject.should_receive( :puts ).with( "@#{TEST_HOST}: sudo pwd" )
144
+
145
+ subject.sudo( 'pwd' )
146
+ end
147
+
148
+ it 'should run the command' do
149
+ subject.stub!( :puts )
150
+
151
+ @ssh.should_receive( :open_channel ) do |&open_channel_block|
152
+ @channel = stub( 'channel' )
153
+
154
+ @channel.should_receive( :request_pty ) do |&request_pty_block|
155
+ request_pty_block.call( @channel, true )
156
+ end
157
+
158
+ @channel.should_receive( :exec ).with( "sudo -p 'sudo_prompt' pwd" )
159
+
160
+ open_channel_block.call @channel
161
+ end
162
+ @ssh.should_receive( :loop )
163
+
164
+ subject.sudo( 'pwd' )
165
+ end
166
+
167
+ context 'in channel' do
168
+
169
+ before :each do
170
+ @rs = Remote::Session.new( TEST_HOST, { :sudo_password => 'secret' } )
171
+ @rs.stub!( :puts )
172
+
173
+ @ssh.stub!( :loop => nil )
174
+ @ch = stub( 'channel' )
175
+ @ssh.stub!( :open_channel ) do |&block|
176
+ block.call @ch
177
+ end
178
+ end
179
+
180
+ it 'should fail if pty request is unsuccessful' do
181
+ @ch.stub!( :request_pty ) do |&block|
182
+ expect do
183
+ block.call( @ch, false )
184
+ end.to raise_error( RuntimeError, 'Could not obtain pty' )
185
+ end
186
+
187
+ @rs.sudo( 'pwd' )
188
+ end
189
+
190
+ context 'with pty' do
191
+
192
+ before :each do
193
+ @ch.stub!( :request_pty ) do |&block|
194
+ block.call( @ch, true )
195
+ end
196
+ end
197
+
198
+ it 'should fail if the command fails' do
199
+ @ch.stub!( :exec ) do |&block|
200
+ expect do
201
+ block.call( @ch, false )
202
+ end.to raise_error( RuntimeError, 'Could not execute sudo command: pwd' )
203
+ end
204
+
205
+ @rs.sudo( 'pwd' )
206
+ end
207
+
208
+ context 'in exec' do
209
+ before :each do
210
+ @ch.stub!( :exec ) do |&block|
211
+ block.call( @ch, true )
212
+ end
213
+ @ch.stub!( :on_extended_data => nil )
214
+ @ch.stub!( :send_data )
215
+ end
216
+
217
+ it 'should output returning data' do
218
+ @ch.stub!( :on_data ) do |&block|
219
+ block.call( @ch, 'some_data' )
220
+ end
221
+
222
+ @rs.should_receive( :puts ).with( 'some_data' )
223
+
224
+ @rs.sudo( 'pwd' )
225
+ end
226
+
227
+ context 'with password prompt' do
228
+ before :each do
229
+ @ch.stub!( :on_data ) do |&block|
230
+ block.call( @ch, 'sudo_prompt' )
231
+ end
232
+ end
233
+
234
+ it 'should supply the sudo password, when prompted' do
235
+ @ch.should_receive( :send_data ).with( "secret\n" )
236
+
237
+ @rs.sudo( 'pwd' )
238
+ end
239
+
240
+ it 'should not echo the standard prompt' do
241
+ output = []
242
+ @rs.stub!( :puts ) do | s |
243
+ output << s
244
+ end
245
+
246
+ @rs.sudo( 'pwd' )
247
+
248
+ output.should == ["@#{TEST_HOST}: sudo pwd"]
249
+ end
250
+ end
251
+
252
+ context 'with user-supplied prompt' do
253
+ before :each do
254
+ @ch.stub!( :on_data ) do |&block|
255
+ block.call( @ch, 'Here is my prompt:' )
256
+ end
257
+ end
258
+
259
+ it 'should send the supplied data' do
260
+ @ch.should_receive( :send_data ).with( "this data\n" )
261
+
262
+ @rs.sudo( 'pwd', 'my prompt' => 'this data' )
263
+ end
264
+
265
+ it 'should not echo the prompt' do
266
+ output = []
267
+ @rs.stub!( :puts ) do | s |
268
+ output << s
269
+ end
270
+
271
+ @rs.sudo( 'pwd', 'my prompt' => 'this data' )
272
+
273
+ output.should == ["@#{TEST_HOST}: sudo pwd"]
274
+ end
275
+
276
+ end
277
+
278
+ it 'should fail if error data is received' do
279
+ @ch.stub!( :on_data )
280
+
281
+ @ch.stub!( :on_extended_data ) do |&block|
282
+ block.call @ch, 'foo', 'It failed'
283
+ end
284
+
285
+ expect do
286
+ @rs.sudo( 'pwd' )
287
+ end.to raise_error( RuntimeError, 'Error It failed while performing command: pwd' )
288
+ end
289
+
290
+ end
291
+
292
+ end
293
+
294
+ end
295
+
296
+ end
297
+
298
+ end
299
+
300
+ end
301
+
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remote-session
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joe Yates
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.7
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.7
30
+ - !ruby/object:Gem::Dependency
31
+ name: net-ssh
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 2.3.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: simplecov
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: ! 'This gem uses Net::SSH to create a connection and allow command execution
79
+ over it.
80
+
81
+ Run commands as the logged on user, or via sudo as any permitetd user (defaults
82
+ to root).'
83
+ email:
84
+ - joe.g.yates@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - .gitignore
90
+ - .travis.yml
91
+ - Gemfile
92
+ - LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - lib/remote/session.rb
96
+ - lib/remote/session/version.rb
97
+ - remote-session.gemspec
98
+ - spec/gather_rspec_coverage.rb
99
+ - spec/spec_helper.rb
100
+ - spec/unit/session_spec.rb
101
+ homepage: ''
102
+ licenses: []
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ segments:
114
+ - 0
115
+ hash: -78875980215889898
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ segments:
123
+ - 0
124
+ hash: -78875980215889898
125
+ requirements: []
126
+ rubyforge_project: nowarning
127
+ rubygems_version: 1.8.24
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: Run user commands, and sudo, commands over an SSH connection
131
+ test_files:
132
+ - spec/gather_rspec_coverage.rb
133
+ - spec/spec_helper.rb
134
+ - spec/unit/session_spec.rb