Chee 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ coverage
7
+ rdoc
8
+ .yardoc
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "Chee/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "Chee"
8
+ s.version = Chee::VERSION
9
+ s.authors = ["da99"]
10
+ s.email = ["i-hate-spam-45671204@mailinator.com"]
11
+ s.homepage = "https://github.com/da99/Chee"
12
+ s.summary = %q{Interact with a SSH session using tty and STDIN.}
13
+ s.description = %q{
14
+ Send commands through SSH, but using a tty/pty and STDIN.
15
+ That's right: Interactive SSH sessions. **Note:** Programs that
16
+ redraw the screen (e.g. vim) don't work that well. Apt-get and
17
+ other programs that request input in a simple manner should work well enough.
18
+ }
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_development_dependency 'bacon'
26
+ s.add_development_dependency 'rake'
27
+ s.add_development_dependency 'Bacon_Colored'
28
+ s.add_development_dependency 'pry'
29
+ s.add_development_dependency 'mocha-on-bacon'
30
+
31
+ # Specify any dependencies here; for example:
32
+ s.add_runtime_dependency 'Get_Set'
33
+ s.add_runtime_dependency 'net-ssh'
34
+ s.add_runtime_dependency 'net-scp'
35
+ end
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ source "http://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in Bob.gemspec
6
+ gemspec
@@ -0,0 +1,81 @@
1
+
2
+ Chee
3
+ ================
4
+
5
+ Send commands through SSH, but using a tty/pty and STDIN.
6
+ That's right: Interactive SSH sessions.
7
+
8
+ Limitations
9
+ -----------
10
+
11
+ * Programs that redraw the screen (e.g. vim) don't work that well.
12
+ Apt-get and
13
+ other programs that request input in a simple manner should work well enough.
14
+
15
+ * PTY (psuedo-terminal) is used. Which means it runs in a sub-shell.
16
+ Which leads to *no* STDERR access. All output is done on STDOUT.
17
+
18
+ Installation
19
+ ------------
20
+
21
+ gem install Chee
22
+
23
+ Usage
24
+ ------
25
+
26
+ require "Chee"
27
+
28
+ Chee.server "my_server" # If you configured server using ~/.ssh/config
29
+ Chee.server Hash[
30
+ :ip => 'localhost',
31
+ :user => 'me',
32
+
33
+ # options other than :ip/:user are sent to Net::SSH
34
+ :password => "try to use private/public keys",
35
+ :timeout => 3
36
+ ]
37
+ Chee.ssh %^ sudo add-apt-repository ppa:nginx/stable ^
38
+ <!-- sudo apt-get install nginx -->
39
+ <!-- ^ -->
40
+
41
+ Or you could include the DSL into your own object:
42
+
43
+ Class My_SSH
44
+
45
+ include Chee::DSL
46
+
47
+ def ssh cmd
48
+ super cmd.strip
49
+ end
50
+
51
+ end # === Class My_SSH
52
+
53
+ Run Tests
54
+ ---------
55
+
56
+ To run the tests:
57
+
58
+ git clone git@github.com:da99/Chee.git
59
+ cd Chee
60
+ bundle update
61
+ bundle exec bacon spec/main.rb
62
+
63
+ Don't forget to setup ssh server, firewall, and passwordless SSH using
64
+ private keys. The following is useful for Ubuntu users:
65
+
66
+ sudo apt-get install ufw openssh-server
67
+ sudo ufw allow from 127.0.0.1 to any port 22
68
+ sudo ufw deny ssh
69
+ sudo ufw default deny
70
+ sudo ufw enable
71
+
72
+ * [UFW: Firewall](https://help.ubuntu.com/community/UFW)
73
+ * Common UFW rules: [http://blog.bodhizazen.net/linux/firewall-ubuntu-desktops/](http://blog.bodhizazen.net/linux/firewall-ubuntu-desktops/)
74
+ * [~/.ssh/config and private keys](http://www.cyberciti.biz/faq/force-ssh-client-to-use-given-private-key-identity-file/)
75
+
76
+ "I hate writing."
77
+ -----------------------------
78
+
79
+ If you know of existing software that makes the above redundant,
80
+ please tell me. The last thing I want to do is maintain code.
81
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,165 @@
1
+ require 'Chee/version'
2
+ require 'Get_Set'
3
+ require 'net/ssh'
4
+ require 'net/scp'
5
+ require 'readline'
6
+
7
+ class Chee
8
+
9
+ Result = Class.new {
10
+ include Get_Set::DSL
11
+ attr_get_set :exit_status, :out, :err
12
+ }
13
+
14
+ Exit_Error = Class.new(RuntimeError) {
15
+ include Get_Set::DSL
16
+ attr_get_set :exit_status, :out, :err
17
+ }
18
+
19
+ module DSL
20
+
21
+ def server *args
22
+ return @server if args.empty?
23
+ @server = args
24
+ end
25
+
26
+ #
27
+ # Thread technique came from:
28
+ # http://stackoverflow.com/questions/6942279/ruby-net-ssh-channel-dies
29
+ #
30
+ def ssh command
31
+ stdout = ""
32
+ stderr = ""
33
+ t = nil # used to store a Thread
34
+
35
+ ip, user, new_opts = *server
36
+ opts = {:timeout=>3}.merge(new_opts || {})
37
+
38
+ begin
39
+
40
+ get_input = true
41
+ @channel = nil
42
+ cmd = ''
43
+ prev_cmd = ''
44
+ prev_data = ''
45
+ result = Result.new
46
+
47
+ t = Thread.new {
48
+
49
+ while get_input do
50
+
51
+ cmd = begin
52
+ Readline.readline("", true).strip
53
+ rescue Interrupt => e # Send CTRL-C:
54
+ get_input = false
55
+ "^C"
56
+ end
57
+
58
+ if @channel
59
+ @channel.process
60
+ else
61
+ print "Connection closed. Could not send: #{cmd}\n"
62
+ end
63
+
64
+ end
65
+
66
+ }
67
+
68
+ Net::SSH.start(ip, user, opts) { |ssh|
69
+
70
+ @channel = ssh.open_channel do |ch1|
71
+
72
+ ch1.on_extended_data do |ch, type, d|
73
+ stderr << d
74
+ end
75
+
76
+ ch1.on_request 'exit-status' do |ch, d|
77
+ result.exit_status d.read_long
78
+ end
79
+
80
+ ch1.on_open_failed { |ch, code, desc|
81
+ stderr << "Failure to open channel: #{code.inspect}: #{desc}"
82
+ }
83
+
84
+ ch1.on_process do |ch|
85
+ if cmd.strip == '^C'
86
+ #ch.close
87
+ ch.send_data( Net::SSH::Buffer.from(:byte, 3, :raw, "\n").to_s )
88
+ stderr << "User requested interrupt."
89
+ else
90
+ if cmd.strip.empty?
91
+ # ignore it
92
+ else
93
+ ch.send_data( "#{cmd}\n" )
94
+ prev_cmd = cmd
95
+ cmd = ''
96
+ end
97
+ end
98
+ end
99
+
100
+ ch1.on_data do |ch, d|
101
+
102
+ stdout << d # .sub(%r!\r?\n\Z!,'')
103
+
104
+ unless prev_cmd.to_s.strip == d.strip
105
+ print d
106
+ STDOUT.flush
107
+ end
108
+
109
+ prev_data = d
110
+ end
111
+
112
+ ch1.request_pty do |ch, success|
113
+ if success
114
+ # do nothing
115
+ else
116
+ ch.close
117
+ (stderr << "Unknown error requesting pty.")
118
+ end
119
+ end
120
+
121
+ ch1.exec(command)
122
+
123
+ end
124
+
125
+ ssh.loop 0.1
126
+ } # === Net::SSH.start
127
+
128
+ rescue Timeout::Error => e
129
+ raise e.class, server.inspect
130
+
131
+ rescue Net::SSH::AuthenticationFailed => e
132
+ raise e.class, "Using: #{server.inspect}"
133
+
134
+ #rescue Net::SSH::HostKeyMismatch => e
135
+ # if e.message[%r!fingerprint .+ does not match for!]
136
+ # print "Try this", "ssh-keygen -f \"~/.ssh/known_hosts\" -R #{server[:ip]}\n"
137
+ # raise Retry_Command, "Removed the RSA key."
138
+ # end
139
+ #
140
+ # raise e
141
+ ensure
142
+ get_input = false
143
+ t.exit if t
144
+ end
145
+
146
+ result.err stderr
147
+ result.out stdout
148
+
149
+ if !result.err.empty? || result.exit_status != 0
150
+ e = Exit_Error.new("Exit: #{result.exit_status}, STDERR: #{result.err}")
151
+ e.exit_status result.exit_status
152
+ e.out result.out
153
+ e.err result.err
154
+ raise e
155
+ end
156
+
157
+ result.out.strip!
158
+ result
159
+ end
160
+
161
+ end # === module DSL
162
+
163
+ extend DSL
164
+
165
+ end # === class Chee
@@ -0,0 +1,3 @@
1
+ class Chee
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+
2
+ STDERR.puts "err msg 1"
3
+ STDERR.puts "err msg 2"
4
+ exit 0
5
+
6
+
@@ -0,0 +1,2 @@
1
+ puts "Exiting"
2
+ exit 2
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.print e.message, "\n"
7
+ $stderr.print "Run `bundle install` to install missing gems\n"
8
+ exit e.status_code
9
+ end
10
+ require 'bacon'
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+
15
+ Bacon.summary_on_exit
@@ -0,0 +1,16 @@
1
+
2
+ require File.expand_path('spec/helper')
3
+ require 'Chee'
4
+ require 'Bacon_Colored'
5
+ require 'pry'
6
+ require 'mocha-on-bacon'
7
+
8
+
9
+ # ======== Include the tests.
10
+ if ARGV.size > 1 && ARGV[1, ARGV.size - 1].detect { |a| File.exists?(a) }
11
+ # Do nothing. Bacon grabs the file.
12
+ else
13
+ Dir.glob('spec/tests/*.rb').each { |file|
14
+ require File.expand_path(file.sub('.rb', '')) if File.file?(file)
15
+ }
16
+ end
@@ -0,0 +1,84 @@
1
+
2
+ describe ":ssh_exec" do
3
+
4
+ before do
5
+ @localhost = 'localhost'
6
+
7
+ @wrong_ip = Hash[
8
+ :ip=> 'localhosts',
9
+ :user=>`whoami`.strip
10
+ ]
11
+
12
+ Chee.server @localhost
13
+ end
14
+
15
+ it 'accepts a String for the server info.' do
16
+ Chee.server "localhost"
17
+ Chee.ssh("echo 'a'").out.should == "a"
18
+ end
19
+
20
+ it 'accepts a Hash for the server info.' do
21
+ Chee.server 'localhost', `whoami`.strip, :password=>nil
22
+ Chee.ssh("echo 'b'").out.should == 'b'
23
+ end
24
+
25
+ it 'uses a PTY' do
26
+ Chee.ssh("tty").out.should.match %r!/dev/pts/\d+!
27
+ end
28
+
29
+ it 'returns a SSH::Results' do
30
+ Chee.ssh("hostname").should.be.is_a Chee::Result
31
+ end
32
+
33
+ it "strips returned data" do
34
+ target = `uptime`.strip.gsub(%r!\d+!, '[0-9]{1,2}')
35
+ Chee.ssh("uptime").out.should.match %r!#{target}!
36
+ end
37
+
38
+ it 'raises Net::SSH::AuthenticationFailed if login/password are incorrect' do
39
+ lambda {
40
+ Chee.server "github.com"
41
+ Chee.ssh "hostname"
42
+ }.should.raise(Net::SSH::AuthenticationFailed)
43
+ .message.should.match %r!Using: ..github.com..!
44
+
45
+ end
46
+
47
+ it 'raises Chee::Exit_Error if return status is not zero' do
48
+ e = lambda {
49
+ Chee.ssh "HOSTNAMES"
50
+ }.should.raise(Chee::Exit_Error)
51
+
52
+ e.exit_status.should == 127
53
+ end
54
+
55
+ end # === describe :ssh_exec
56
+
57
+ __END__
58
+ describe ":ssh_exits" do
59
+
60
+ it 'captures exits based on key => int, val => Regexp' do
61
+ lambda {
62
+ ignore_exits("cat something.txt", 1=>%r!something\.txt\: No such file or directory!)
63
+ }.should.not.raise
64
+ end
65
+
66
+ it 'captures exits based on key => int, val => String' do
67
+ lambda {
68
+ ignore_exits("cat something.txt", 1=>'something.txt: No such file or directory')
69
+ }.should.not.raise
70
+ end
71
+
72
+ it 'returns SSH::Results for a non-zero exit status' do
73
+ ignore_exits("cat something.txt", 1=>'something.txt: No such file or directory')
74
+ .should.be.is_a Unified_IO::Remote::SSH::Results
75
+ end
76
+
77
+ it 'returns SSH::Results for a zero exit status' do
78
+ ignore_exits("uptime", 1=>'something.txt: No such file or directory')
79
+ .should.be.is_a Unified_IO::Remote::SSH::Results
80
+ end
81
+
82
+ end # === describe :ssh_exits
83
+
84
+
@@ -0,0 +1,13 @@
1
+
2
+ bins = Dir.glob("bin/*")
3
+
4
+ unless bins.empty?
5
+ describe "permissions of bin/" do
6
+ bins.each { |file|
7
+ it "should chmod 755 for: #{file}" do
8
+ `stat -c %a #{file}`.strip
9
+ .should.be == "755"
10
+ end
11
+ }
12
+ end # === permissions of bin/
13
+ end # === unless bins.empty?
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Chee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - da99
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bacon
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
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: Bacon_Colored
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '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: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry
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
+ - !ruby/object:Gem::Dependency
79
+ name: mocha-on-bacon
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: Get_Set
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: net-ssh
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: net-scp
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: ! "\n Send commands through SSH, but using a tty/pty and STDIN.\n
143
+ \ That's right: Interactive SSH sessions. **Note:** Programs that\n redraw
144
+ the screen (e.g. vim) don't work that well. Apt-get and \n other programs that
145
+ request input in a simple manner should work well enough.\n "
146
+ email:
147
+ - i-hate-spam-45671204@mailinator.com
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - .gitignore
153
+ - Chee.gemspec
154
+ - Gemfile
155
+ - README.md
156
+ - Rakefile
157
+ - lib/Chee.rb
158
+ - lib/Chee/version.rb
159
+ - spec/files/errors_with_exit_0.rb
160
+ - spec/files/exit_with_2.rb
161
+ - spec/helper.rb
162
+ - spec/main.rb
163
+ - spec/tests/Chee.rb
164
+ - spec/tests/bin.rb
165
+ homepage: https://github.com/da99/Chee
166
+ licenses: []
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ! '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ required_rubygems_version: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ requirements: []
184
+ rubyforge_project:
185
+ rubygems_version: 1.8.19
186
+ signing_key:
187
+ specification_version: 3
188
+ summary: Interact with a SSH session using tty and STDIN.
189
+ test_files: []