remote-session 0.0.1

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/.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