Chee 0.1.0

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.
@@ -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: []