ssh 1.0.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.
Binary file
@@ -0,0 +1,27 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/isolate'
4
+ require "autotest/restart"
5
+
6
+ Autotest.add_hook :initialize do |at|
7
+ at.testlib = "minitest/autorun"
8
+ at.add_exception "tmp"
9
+
10
+ # at.extra_files << "../some/external/dependency.rb"
11
+ #
12
+ # at.libs << ":../some/external"
13
+ #
14
+ # at.add_exception "vendor"
15
+ #
16
+ # at.add_mapping(/dependency.rb/) do |f, _|
17
+ # at.files_matching(/test_.*rb$/)
18
+ # end
19
+ #
20
+ # %w(TestA TestB).each do |klass|
21
+ # at.extra_class_map[klass] = "test/test_misc.rb"
22
+ # end
23
+ end
24
+
25
+ # Autotest.add_hook :run_command do |at|
26
+ # system "rake build"
27
+ # end
File without changes
@@ -0,0 +1,5 @@
1
+ === 1.0.0 / 2011-11-03
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday! Sorta... Extracted from rake-remote_task 2.0.5.
@@ -0,0 +1,7 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/ssh.rb
7
+ test/test_ssh.rb
@@ -0,0 +1,58 @@
1
+ = SSH
2
+
3
+ home :: https://github.com/seattlerb/ssh
4
+ rdoc :: http://docs.seattlerb.org/ssh
5
+
6
+ == DESCRIPTION:
7
+
8
+ SSH provides a simple streaming ssh command runner. That's it.
9
+ This is a one trick pony.
10
+
11
+ ssh = SSH.new "example.com", "/var/log"
12
+ puts ssh.run "ls"
13
+
14
+ SSH was extracted from rake-remote_task which was extracted from vlad.
15
+
16
+ == FEATURES/PROBLEMS:
17
+
18
+ * Provides a simple streaming ssh command runner.
19
+ * There is no 2
20
+ * How the hell was this gem name not used already?
21
+
22
+ == SYNOPSIS:
23
+
24
+ ssh = SSH.new "example.com", "/var/log"
25
+ puts ssh.run "ls"
26
+
27
+ == REQUIREMENTS:
28
+
29
+ * open4
30
+
31
+ == INSTALL:
32
+
33
+ * sudo gem install ssh
34
+
35
+ == LICENSE:
36
+
37
+ (The MIT License)
38
+
39
+ Copyright (c) Ryan Davis, seattle.rb
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining
42
+ a copy of this software and associated documentation files (the
43
+ 'Software'), to deal in the Software without restriction, including
44
+ without limitation the rights to use, copy, modify, merge, publish,
45
+ distribute, sublicense, and/or sell copies of the Software, and to
46
+ permit persons to whom the Software is furnished to do so, subject to
47
+ the following conditions:
48
+
49
+ The above copyright notice and this permission notice shall be
50
+ included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
53
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
55
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
56
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
57
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
58
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+
6
+ Hoe.plugin :isolate
7
+ Hoe.plugin :seattlerb
8
+
9
+ Hoe.spec "ssh" do
10
+ developer "Ryan Davis", "ryand-ruby@zenspider.com"
11
+
12
+ # dependency 'rake', '~> 0.8'
13
+ dependency 'open4', '~> 1.0'
14
+ end
15
+
16
+ # vim: syntax=ruby
@@ -0,0 +1,110 @@
1
+ require 'rubygems'
2
+ require 'open4'
3
+
4
+ ##
5
+ # SSH provides a simple streaming ssh command runner. That's it.
6
+ # This is a one trick pony.
7
+ #
8
+ # ssh = SSH.new "example.com", "/var/log"
9
+ # puts ssh.run "ls"
10
+ #
11
+ # SSH was extracted from rake-remote_task which was extracted from vlad.
12
+ #
13
+ # SSH's idea contributed by Joel Parker Henderson.
14
+
15
+ class SSH
16
+ VERSION = "1.0.0"
17
+
18
+ class Error < RuntimeError; end
19
+
20
+ class CommandFailedError < Error
21
+ attr_reader :status
22
+ def initialize status
23
+ @status = status
24
+ end
25
+ end
26
+
27
+ include Open4
28
+
29
+ attr_accessor :ssh_cmd, :ssh_flags, :target_host, :target_dir
30
+ attr_accessor :sudo_prompt, :sudo_password
31
+
32
+ def initialize target_host = nil, target_dir = nil
33
+ self.ssh_cmd = "ssh"
34
+ self.ssh_flags = []
35
+ self.target_host = target_host
36
+ self.target_dir = target_dir
37
+
38
+ self.sudo_prompt = /^Password:/
39
+ self.sudo_password = nil
40
+ end
41
+
42
+ def run command
43
+ command = "cd #{target_dir} && #{command}" if target_dir
44
+ cmd = [ssh_cmd, ssh_flags, target_host, command].flatten
45
+
46
+ if $DEBUG then
47
+ trace = [ssh_cmd, ssh_flags, target_host, "'#{command}'"]
48
+ warn trace.flatten.join ' '
49
+ end
50
+
51
+ pid, inn, out, err = popen4(*cmd)
52
+
53
+ status, result = empty_streams pid, inn, out, err
54
+
55
+ unless status.success? then
56
+ e = status.exitstatus
57
+ c = cmd.join ' '
58
+ raise(CommandFailedError.new(status), "Failed with status #{e}: #{c}")
59
+ end
60
+
61
+ result.join
62
+ ensure
63
+ inn.close rescue nil
64
+ out.close rescue nil
65
+ err.close rescue nil
66
+ end
67
+
68
+ def empty_streams pid, inn, out, err
69
+ result = []
70
+ inn.sync = true
71
+ streams = [out, err]
72
+ out_stream = {
73
+ out => $stdout,
74
+ err => $stderr,
75
+ }
76
+
77
+ # Handle process termination ourselves
78
+ status = nil
79
+ Thread.start do
80
+ status = Process.waitpid2(pid).last
81
+ end
82
+
83
+ until streams.empty? do
84
+ # don't busy loop
85
+ selected, = select streams, nil, nil, 0.1
86
+
87
+ next if selected.nil? or selected.empty?
88
+
89
+ selected.each do |stream|
90
+ if stream.eof? then
91
+ streams.delete stream if status # we've quit, so no more writing
92
+ next
93
+ end
94
+
95
+ data = stream.readpartial(1024)
96
+ out_stream[stream].write data
97
+
98
+ if stream == err and data =~ sudo_prompt then
99
+ inn.puts sudo_password
100
+ data << "\n"
101
+ $stderr.write "\n"
102
+ end
103
+
104
+ result << data
105
+ end
106
+ end
107
+
108
+ return status, result
109
+ end
110
+ end
@@ -0,0 +1,132 @@
1
+ require "minitest/autorun"
2
+ require "ssh"
3
+
4
+ require 'stringio'
5
+
6
+ class StringIO
7
+ def readpartial(size) read end # suck!
8
+ end
9
+
10
+ module Process
11
+ def self.expected status
12
+ @@expected ||= []
13
+ @@expected << status
14
+ end
15
+
16
+ class << self
17
+ alias :waitpid2_old :waitpid2
18
+
19
+ def waitpid2(pid)
20
+ [ @@expected.shift ]
21
+ end
22
+ end
23
+ end
24
+
25
+ class SSH
26
+ attr_accessor :commands, :action, :input, :output, :error
27
+
28
+ class Status < Struct.new :exitstatus
29
+ def success?() exitstatus == 0 end
30
+ end
31
+
32
+ def popen4 *command
33
+ @commands << command
34
+
35
+ @input = StringIO.new
36
+ out = StringIO.new @output.shift.to_s
37
+ err = StringIO.new @error.shift.to_s
38
+
39
+ raise if block_given?
40
+
41
+ status = self.action ? self.action[command.join(' ')] : 0
42
+ Process.expected Status.new(status)
43
+
44
+ return 42, @input, out, err
45
+ end
46
+
47
+ def select reads, writes, errs, timeout
48
+ [reads, writes, errs]
49
+ end
50
+ end
51
+
52
+ class TestSSH < MiniTest::Unit::TestCase
53
+ def setup
54
+ super
55
+ @ssh = SSH.new
56
+ @ssh.commands = []
57
+ @ssh.output = []
58
+ @ssh.error = []
59
+ @ssh.action = nil
60
+ end
61
+
62
+ def test_run
63
+ @ssh.output << "file1\nfile2\n"
64
+ @ssh.target_host = "app.example.com"
65
+ result = nil
66
+
67
+ out, err = capture_io do
68
+ result = @ssh.run("ls")
69
+ end
70
+
71
+ commands = @ssh.commands
72
+
73
+ assert_equal 1, commands.size, 'not enough commands'
74
+ assert_equal ["ssh", "app.example.com", "ls"],
75
+ commands.first, 'app'
76
+ assert_equal "file1\nfile2\n", result
77
+
78
+ assert_equal "file1\nfile2\n", out
79
+ assert_equal '', err
80
+ end
81
+
82
+ def test_run_dir
83
+ @ssh.target_host = "app.example.com"
84
+ @ssh.target_dir = "/www/dir1"
85
+
86
+ @ssh.run("ls")
87
+
88
+ commands = @ssh.commands
89
+
90
+ assert_equal [["ssh", "app.example.com", "cd /www/dir1 && ls"]], commands
91
+ end
92
+
93
+ def test_run_failing_command
94
+ @ssh.input = StringIO.new "file1\nfile2\n"
95
+ @ssh.target_host = 'app.example.com'
96
+ @ssh.action = proc { 1 }
97
+
98
+ e = assert_raises(SSH::CommandFailedError) { @ssh.run("ls") }
99
+ assert_equal "Failed with status 1: ssh app.example.com ls", e.message
100
+
101
+ assert_equal [["ssh", "app.example.com", "ls"]], @ssh.commands
102
+ end
103
+
104
+ def test_run_sudo
105
+ @ssh.output << "file1\nfile2\n"
106
+ @ssh.error << 'Password:'
107
+ @ssh.target_host = "app.example.com"
108
+ @ssh.sudo_password = "my password"
109
+ result = nil
110
+
111
+ out, err = capture_io do
112
+ result = @ssh.run("sudo ls")
113
+ end
114
+
115
+ commands = @ssh.commands
116
+
117
+ assert_equal 1, commands.size, 'not enough commands'
118
+ assert_equal ['ssh', 'app.example.com', 'sudo ls'],
119
+ commands.first
120
+
121
+ assert_equal "my password\n", @ssh.input.string
122
+
123
+ # WARN: Technically incorrect, the password line should be
124
+ # first... this is an artifact of changes to the IO code in run
125
+ # and the fact that we have a very simplistic (non-blocking)
126
+ # testing model.
127
+ assert_equal "file1\nfile2\nPassword:\n", result
128
+
129
+ assert_equal "file1\nfile2\n", out
130
+ assert_equal "Password:\n", err
131
+ end
132
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssh
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Ryan Davis
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain:
17
+ - |
18
+ -----BEGIN CERTIFICATE-----
19
+ MIIDPjCCAiagAwIBAgIBADANBgkqhkiG9w0BAQUFADBFMRMwEQYDVQQDDApyeWFu
20
+ ZC1ydWJ5MRkwFwYKCZImiZPyLGQBGRYJemVuc3BpZGVyMRMwEQYKCZImiZPyLGQB
21
+ GRYDY29tMB4XDTA5MDMwNjE4NTMxNVoXDTEwMDMwNjE4NTMxNVowRTETMBEGA1UE
22
+ AwwKcnlhbmQtcnVieTEZMBcGCgmSJomT8ixkARkWCXplbnNwaWRlcjETMBEGCgmS
23
+ JomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALda
24
+ b9DCgK+627gPJkB6XfjZ1itoOQvpqH1EXScSaba9/S2VF22VYQbXU1xQXL/WzCkx
25
+ taCPaLmfYIaFcHHCSY4hYDJijRQkLxPeB3xbOfzfLoBDbjvx5JxgJxUjmGa7xhcT
26
+ oOvjtt5P8+GSK9zLzxQP0gVLS/D0FmoE44XuDr3iQkVS2ujU5zZL84mMNqNB1znh
27
+ GiadM9GHRaDiaxuX0cIUBj19T01mVE2iymf9I6bEsiayK/n6QujtyCbTWsAS9Rqt
28
+ qhtV7HJxNKuPj/JFH0D2cswvzznE/a5FOYO68g+YCuFi5L8wZuuM8zzdwjrWHqSV
29
+ gBEfoTEGr7Zii72cx+sCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAw
30
+ HQYDVR0OBBYEFEfFe9md/r/tj/Wmwpy+MI8d9k/hMA0GCSqGSIb3DQEBBQUAA4IB
31
+ AQAY59gYvDxqSqgC92nAP9P8dnGgfZgLxP237xS6XxFGJSghdz/nI6pusfCWKM8m
32
+ vzjjH2wUMSSf3tNudQ3rCGLf2epkcU13/rguI88wO6MrE0wi4ZqLQX+eZQFskJb/
33
+ w6x9W1ur8eR01s397LSMexySDBrJOh34cm2AlfKr/jokKCTwcM0OvVZnAutaovC0
34
+ l1SVZ0ecg88bsWHA0Yhh7NFxK1utWoIhtB6AFC/+trM0FQEB/jZkIS8SaNzn96Rl
35
+ n0sZEf77FLf5peR8TP/PtmIg7Cyqz23sLM4mCOoTGIy5OcZ8TdyiyINUHtb5ej/T
36
+ FBHgymkyj/AOSqKRIpXPhjC6
37
+ -----END CERTIFICATE-----
38
+
39
+ date: 2011-11-15 00:00:00 Z
40
+ dependencies:
41
+ - !ruby/object:Gem::Dependency
42
+ name: open4
43
+ prerelease: false
44
+ requirement: &id001 !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ~>
48
+ - !ruby/object:Gem::Version
49
+ hash: 15
50
+ segments:
51
+ - 1
52
+ - 0
53
+ version: "1.0"
54
+ type: :runtime
55
+ version_requirements: *id001
56
+ - !ruby/object:Gem::Dependency
57
+ name: rdoc
58
+ prerelease: false
59
+ requirement: &id002 !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ hash: 21
65
+ segments:
66
+ - 3
67
+ - 9
68
+ version: "3.9"
69
+ type: :development
70
+ version_requirements: *id002
71
+ - !ruby/object:Gem::Dependency
72
+ name: minitest
73
+ prerelease: false
74
+ requirement: &id003 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ hash: 13
80
+ segments:
81
+ - 2
82
+ - 7
83
+ version: "2.7"
84
+ type: :development
85
+ version_requirements: *id003
86
+ - !ruby/object:Gem::Dependency
87
+ name: hoe
88
+ prerelease: false
89
+ requirement: &id004 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ hash: 27
95
+ segments:
96
+ - 2
97
+ - 12
98
+ version: "2.12"
99
+ type: :development
100
+ version_requirements: *id004
101
+ description: |-
102
+ SSH provides a simple streaming ssh command runner. That's it.
103
+ This is a one trick pony.
104
+
105
+ ssh = SSH.new "example.com", "/var/log"
106
+ puts ssh.run "ls"
107
+
108
+ SSH was extracted from rake-remote_task which was extracted from vlad.
109
+ email:
110
+ - ryand-ruby@zenspider.com
111
+ executables: []
112
+
113
+ extensions: []
114
+
115
+ extra_rdoc_files:
116
+ - History.txt
117
+ - Manifest.txt
118
+ - README.txt
119
+ files:
120
+ - .autotest
121
+ - History.txt
122
+ - Manifest.txt
123
+ - README.txt
124
+ - Rakefile
125
+ - lib/ssh.rb
126
+ - test/test_ssh.rb
127
+ - .gemtest
128
+ homepage: https://github.com/seattlerb/ssh
129
+ licenses: []
130
+
131
+ post_install_message:
132
+ rdoc_options:
133
+ - --main
134
+ - README.txt
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ hash: 3
152
+ segments:
153
+ - 0
154
+ version: "0"
155
+ requirements: []
156
+
157
+ rubyforge_project: ssh
158
+ rubygems_version: 1.8.10
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: SSH provides a simple streaming ssh command runner
162
+ test_files:
163
+ - test/test_ssh.rb
Binary file