rye 0.3.2 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.txt CHANGED
@@ -1,5 +1,25 @@
1
1
  RYE, CHANGES
2
2
 
3
+ TODO
4
+
5
+ * Fingerprints: ssh-keygen -l -f id_rsa_repos.pub
6
+
7
+
8
+ #### 0.4.0 (2009-04-06) #############################
9
+
10
+ * ADDED: to_s and inspect methods for cleaner debugging output
11
+ * ADDED: == method for Rye::Box
12
+ * ADDED: exit code and exit signal to Rye::Rap objects
13
+ * ADDED: commands now raise a Rye::CommandError exception
14
+ when the command returns an exit code greater than 0.
15
+ * CHANGED: Box.add_command renamed to Box.run_command
16
+ * FIXED: Box.run_command was parsing arguments incorrectly
17
+ * FIXED: Box.net_ssh_exec was working on nil stderr
18
+ * FIXED: bin/try handles the new command exceptions
19
+ * ADDED: Command switches can now be sent as Symbols (rbox.ls(:h))
20
+ * ADDED: Rye.host_keys
21
+ * ADDED: bin/rye
22
+
3
23
 
4
24
  #### 0.3.2 (2009-04-05) #############################
5
25
 
data/README.rdoc CHANGED
@@ -1,10 +1,10 @@
1
- = Rye - v0.3
1
+ = Rye - v0.4
2
2
 
3
3
  Safely run remote commands via SSH in Ruby.
4
4
 
5
5
  Rye is similar to Rush[http://rush.heroku.com] but everything happens over SSH (no HTTP daemon) and the default settings are less powerful (for safety). For example, file globs are disabled so unless otherwise specified, you can't do this: <tt>rbox.rm('-rf', '/etc/**/*')</tt>.
6
6
 
7
- See bin/try for working examples.
7
+ See the examples below (which are taken from bin/try).
8
8
 
9
9
  == Installation
10
10
 
@@ -20,7 +20,7 @@ One of:
20
20
  rbox = Rye::Box.new('localhost')
21
21
 
22
22
  # Commands are run as methods on the Rye::Box object
23
- puts rbox.uptime # => 11:02 up 16:01, 3 users
23
+ puts rbox.uptime # => 11:02 up 16:01, 3 users
24
24
 
25
25
  # The response value for all commands is a Rye::Rap object. The rap is a
26
26
  # subclass of Array so you can treat it as an Array, but it can also act
@@ -57,12 +57,9 @@ One of:
57
57
  # Safe-mode is enabled by default. In safe-mode, all command
58
58
  # arguments are thoroughly escaped. This prevents access to
59
59
  # environment variables and file globs (among other things).
60
- p rbox_safe.echo('$HOME') # => "$HOME"
61
- p rbox_safe['/etc'].ls('host*') # =>
62
- p rbox_safe.ls('-l | wc -l') # =>
63
- p rbox_safe.echo('$HOME > /tmp/rye-home') # => "$HOME > /tmp/home"
64
- p rbox_safe.cat('/tmp/rye-home') # =>
65
- p rbox_safe.cat('/tmp/rye-home').stderr # => "No such file or directory"
60
+ p rbox_safe.echo('$HOME') # => "$HOME"
61
+ p rbox_safe['/etc'].ls('host*') rescue Rye::CommandError # Doesn't exist
62
+ p rbox_safe.ls('-l | wc -l') rescue Rye::CommandError # => '|' is not a valid ls arg
66
63
 
67
64
  # Here's the same commands with safe-mode disabled:
68
65
  p rbox_wild.echo('$HOME') # => "/home/rye"
@@ -70,8 +67,6 @@ One of:
70
67
  p rbox_wild.ls('-l | wc -l') # => 110
71
68
  p rbox_wild.echo('$HOME > /tmp/rye-home') # =>
72
69
  p rbox_wild.cat('/tmp/rye-home') # => "/home/rye"
73
- p rbox_wild.rm('/tmp/rye-home') # =>
74
-
75
70
 
76
71
 
77
72
  == EXAMPLE 3 -- Custom Commands
@@ -86,10 +81,10 @@ One of:
86
81
  # automatically become available to all Rye::Box objects.
87
82
  module Rye::Cmd
88
83
  def rye9000(*args)
89
- add_command("ls", args)
84
+ run_command("ls", args)
90
85
  end
91
86
  def somescript(*args)
92
- add_command("/path/to/my/script", args)
87
+ run_command("/path/to/my/script", args)
93
88
  end
94
89
  end
95
90
 
@@ -132,6 +127,52 @@ One of:
132
127
  p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
133
128
 
134
129
 
130
+ == EXAMPLE 5 -- ERROR HANDLING
131
+
132
+ rbox = Rye::Box.new('localhost', :safe => false)
133
+
134
+ # Rye follows the standard convention of taking exception to a non-zero
135
+ # exit code by raising a Rye::CommandError. In this case, rye9000.test
136
+ # is not found by the ls command.
137
+ begin
138
+ rbox.ls('rye9000.test')
139
+ rescue Rye::CommandError => ex
140
+ puts ex.exit_code # => 1
141
+ puts ex.stderr # => ls: rye.test: No such file or directory
142
+ end
143
+
144
+ # The Rye:Rap response objects also give you the STDOUT and STDERR
145
+ # content separately. Here we redirect STDOUT to STDERR, so this
146
+ # will return nothing:
147
+ puts rbox.uname('-a 1>&2').stdout # =>
148
+
149
+ # It all went to STDERR:
150
+ puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ...
151
+
152
+ # There were no actual error so the exit code should be 0.
153
+ puts rbox.uname('-a 1>&2').exit_code # => 0
154
+
155
+ == EXAMPLE 6 -- rye
156
+
157
+ Rye comes with a command-line utility called <tt>rye</tt>.
158
+
159
+ # Prints the paths to your private keys
160
+ $ rye keys
161
+
162
+ # Prints the server host keys (suitable for ~/.ssh/known_hosts)
163
+ $ rye hostkey HOST1 HOST2
164
+
165
+ # Add your public keys to authorized_keys and authorized_keys2
166
+ # on a remote machine
167
+ $ rye authorize HOST1 HOST2
168
+
169
+ More info:
170
+
171
+ $ rye -h
172
+ $ rye COMMAND -h
173
+ $ rye show-commands
174
+
175
+
135
176
  == About Safe-Mode
136
177
 
137
178
  In safe-mode:
@@ -148,21 +189,26 @@ Using a Ruby interface to execute shell commands is pretty awesome, particularly
148
189
 
149
190
  == Command Whitelist
150
191
 
151
- Rye permits only a limited number of system commands to be run. This default whitelist is defined in Rye::Cmd but you can add your own commands as you please (see Example 2).
152
-
153
- == Credits
192
+ Rye permits only a limited number of system commands to be run. This default whitelist is defined in Rye::Cmd[http://github.com/delano/rye/blob/master/lib/rye/cmd.rb] but you can add your own commands as you please (see Example 3).
154
193
 
155
- * Delano Mandelbaum (delano@solutious.com)
194
+ == Dependencies
156
195
 
196
+ * OpenSSL[http://www.openssl.org] (The C library)
197
+ * Ruby Gems:
198
+ * net-ssh
199
+ * net-scp
200
+ * highline
201
+ * drydock
157
202
 
158
- == Thanks
203
+ == Known Issues
159
204
 
160
- * Solutious Incorporated (http://solutious.com) for all the orange juice.
161
- * The country of Canada for making Rye Whisky.
205
+ This list will grow. If you find one let me know!
162
206
 
207
+ * Rye doesn't read the ~/.ssh/config file yet
163
208
 
164
- == Kudos
209
+ == Thanks
165
210
 
211
+ * Solutious Incorporated (http://solutious.com) for all the orange juice.
166
212
  * http://github.com/adamwiggins/rush
167
213
  * http://github.com/jamis/capistrano/blob/master/lib/capistrano/shell.rb
168
214
  * http://www.nofluffjuststuff.com/blog/david_bock/2008/10/ruby_s_closure_cleanup_idiom_and_net_ssh.html
@@ -178,7 +224,7 @@ Rye permits only a limited number of system commands to be run. This default whi
178
224
  == Credits
179
225
 
180
226
  * Delano Mandelbaum (delano@solutious.com)
181
-
227
+ * Escape, Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
182
228
 
183
229
  == License
184
230
 
data/bin/rye ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # Rye -- A CLI for some handy SSH tools
4
+ #
5
+ # If your reading this via the rdocs you won't be able to see the code
6
+ # See: http://github.com/delano/rye/blob/master/bin/rye
7
+ #
8
+ # Usage: rye
9
+ #
10
+
11
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
12
+
13
+ require 'rubygems'
14
+ require 'stringio'
15
+ require 'yaml'
16
+ require 'drydock'
17
+ require 'rye'
18
+
19
+ include Drydock
20
+
21
+ global :p, :path, String, "A directory containing SSH private keys or the path to a private key"
22
+
23
+ before do |obj|
24
+ STDERR.puts
25
+ # Load private keys if specified
26
+ if obj.global.path
27
+ keys = Rye.find_private_keys(obj.global.path)
28
+ Rye.add_keys(keys)
29
+ end
30
+ end
31
+
32
+ after do
33
+ STDERR.puts
34
+ end
35
+
36
+ desc "Add your public keys to one or more remote machines"
37
+ option :u, :user, String, "Username"
38
+ argv :hostname
39
+ command :authorize do |obj|
40
+ raise "You must specify a host" unless obj.argv.hostname
41
+
42
+ highline = HighLine.new # Used for password prompt
43
+
44
+ opts = { :debug => nil }
45
+ opts[:user] = obj.option.user if obj.option.user
46
+
47
+ obj.argv.each do |hostname|
48
+ puts "Authorizing #{hostname}"
49
+
50
+ retried = 0
51
+ begin
52
+ rbox = Rye::Box.new(hostname, opts).connect
53
+
54
+ # We know we're already authorized b/c we didn't have to give a password
55
+ if retried == 0
56
+ puts "%s is already authorized" % rbox.opts[:user]
57
+ puts
58
+ next
59
+ end
60
+
61
+ # An authentication failure means either the requested user doesn't
62
+ # exist on the remote machine or we need to supply a password.
63
+ rescue Net::SSH::AuthenticationFailed => ex
64
+ retried += 1
65
+ opts[:password] = highline.ask("Password: ") { |q| q.echo = '' }
66
+ retry unless retried > 3
67
+ rescue Net::SSH::AuthenticationFailed
68
+ STDERR.puts "Authentication failed."
69
+ exit 1
70
+ end
71
+
72
+ puts "Added public keys for: ", rbox.authorize_keys
73
+
74
+ puts
75
+ end
76
+
77
+ end
78
+ command_alias :authorize, :authorise
79
+
80
+
81
+ desc "Generate a public key from a private key"
82
+
83
+
84
+
85
+ desc "Fetch the host keys for remote machines (suitable for your ~/.ssh/known_hosts file)"
86
+ usage "rye hostkey HOSTNAME [HOSTNAME2 HOSTNAME3 ...]"
87
+ argv :hostname
88
+ command :hostkeys do |obj|
89
+ raise "You must specify a host" unless obj.argv.hostname
90
+ ret = Rye.remote_host_keys(obj.argv.hostname)
91
+ STDERR.puts $/, ret.stderr, $/
92
+ puts ret.stdout
93
+ end
94
+ command_alias :hostkeys, :hostkey
95
+
96
+
97
+ desc "Display your private keys"
98
+ command :keys do |obj|
99
+ Rye.keys.each do |key|
100
+ puts key.join(' ')
101
+ end
102
+ end
103
+
104
+ desc "Display your public SSH keys"
105
+ usage "rye pubkeys [--pem] [KEYPATH]"
106
+ option :p, :pem, "Output in PEM format"
107
+ argv :keypath
108
+ command :pubkeys do |obj|
109
+ keys = obj.argv.empty? ? Rye.keys.collect { |k| k[2] } : obj.argv
110
+ keys.each do |path|
111
+ STDERR.puts "# Public SSH key for #{path}"
112
+ k = Rye::Key.from_file(path)
113
+ puts obj.option.pem ? k.public_key.to_pem : k.public_key.to_ssh2
114
+ end
115
+ end
116
+ command_alias :pubkeys, :pubkey
117
+
118
+
119
+ default :keys
120
+ debug :on
121
+
122
+ # We can Drydock specifically otherwise it will run at_exit. Rye also
123
+ # uses at_exit for shutting down the ssh-agent. Ruby executes at_exit
124
+ # blocks in reverse order so if Drydock is required first, it's block
125
+ # will run after Rye shuts down the ssh-agent.
126
+ begin
127
+ Drydock.run!(ARGV, STDIN) if Drydock.run? && !Drydock.has_run?
128
+ rescue => ex
129
+ STDERR.puts "ERROR (#{ex.class.to_s}): #{ex.message}"
130
+ STDERR.puts ex.backtrace if Drydock.debug?
131
+ end
data/bin/try CHANGED
@@ -24,22 +24,18 @@ puts %Q(
24
24
  rbox = Rye::Box.new('localhost')
25
25
 
26
26
  # Commands are run as methods on the Rye::Box object
27
- puts rbox.uptime # => 11:02 up 16:01, 3 users
27
+ puts rbox.uptime # => 11:02 up 16:01, 3 users
28
28
 
29
29
  # The response value for all commands is a Rye::Rap object. The rap is a
30
30
  # subclass of Array so you can treat it as an Array, but it can also act
31
31
  # like a String if there's only one element.
32
- puts rbox.ls('rye.test') # => ""
33
- puts rbox.ls('rye.test').stderr # => ls: rye.test: No such file or directory
32
+ p rbox.ls(:a, '/') # => ['.', '..', 'bin', 'etc', ...]
34
33
 
35
- puts rbox.touch('rye.test') # => ""
36
- puts rbox.rm('rye.test') # => ""
37
-
38
34
  # You can change directories
39
35
  puts rbox.pwd # => /home/rye
40
36
  puts rbox['/usr/bin'].pwd # => /usr/bin
41
37
  puts rbox.pwd # => /usr/bin
42
- puts rbox.cd('/home/rye').pwd # => /home/rye
38
+ puts rbox.cd(Rye.sysinfo.home).pwd # => /home/rye
43
39
 
44
40
  # You can specify environment variables
45
41
  rbox.add_env(:RYE, "Forty Creek")
@@ -64,12 +60,9 @@ rbox_wild = Rye::Box.new('localhost', :safe => false)
64
60
  # Safe-mode is enabled by default. In safe-mode, all command
65
61
  # arguments are thoroughly escaped. This prevents access to
66
62
  # environment variables and file globs (among other things).
67
- p rbox_safe.echo('$HOME') # => "$HOME"
68
- p rbox_safe['/etc'].ls('host*') # =>
69
- p rbox_safe.ls('-l | wc -l') # =>
70
- p rbox_safe.echo('$HOME > /tmp/rye-home') # => "$HOME > /tmp/home"
71
- p rbox_safe.cat('/tmp/rye-home') # =>
72
- p rbox_safe.cat('/tmp/rye-home').stderr # => "No such file or directory"
63
+ p rbox_safe.echo('$HOME') # => "$HOME"
64
+ p rbox_safe['/etc'].ls('host*') rescue Rye::CommandError # Doesn't exist
65
+ p rbox_safe.ls('-l | wc -l') rescue Rye::CommandError # => '|' is not a valid ls arg
73
66
 
74
67
  # Here's the same commands with safe-mode disabled:
75
68
  p rbox_wild.echo('$HOME') # => "/home/rye"
@@ -77,7 +70,7 @@ p rbox_wild['/etc'].ls('host*') # => ["hostconfig", "hosts"]
77
70
  p rbox_wild.ls('-l | wc -l') # => 110
78
71
  p rbox_wild.echo('$HOME > /tmp/rye-home') # =>
79
72
  p rbox_wild.cat('/tmp/rye-home') # => "/home/rye"
80
- p rbox_wild.rm('/tmp/rye-home') # =>
73
+
81
74
 
82
75
 
83
76
  puts %Q(
@@ -95,10 +88,10 @@ p rbox.commands.member?('rye9000') # => false
95
88
  # automatically become available to all Rye::Box objects.
96
89
  module Rye::Cmd
97
90
  def rye9000(*args)
98
- add_command("ls", args)
91
+ run_command("ls", args)
99
92
  end
100
93
  def somescript(*args)
101
- add_command("/path/to/my/script", args)
94
+ run_command("/path/to/my/script", args)
102
95
  end
103
96
  end
104
97
 
@@ -121,7 +114,7 @@ rset.add_boxes(rbox, 'localhost') # Add boxes as hostnames or objects
121
114
  # Calling methods on Rye::Set objects is very similar to calling them on
122
115
  # Rye::Box objects. In fact, it's identical:
123
116
  p rset.uptime # => [[14:19:02 up 32 days, 19:35 ...], [14:19:02 up 30 days, 01:35]]
124
- p rset['/etc'].ls # => [['file1', 'file2', ...], ['life1', 'life2', ...]]
117
+ p rset['/usr'].ls # => [['bin', 'etc', ...], ['bin', 'etc', ...]]
125
118
 
126
119
  # Like Rye::Box, the response value is a Rye::Rap object containing the
127
120
  # responses from each box. Each response is itself a Rye::Rap object.
@@ -144,5 +137,35 @@ rset.add_env(:RYE, "Forty Creek")
144
137
  p rset.env.first.select { |env| env =~ /RYE/ } # => ["RYE=Forty Creek"]
145
138
 
146
139
 
140
+ puts %Q(
141
+ # ------------------------------------------------------------------
142
+ # EXAMPLE 5 -- ERROR HANDLING
143
+ #)
144
+
145
+ rbox = Rye::Box.new('localhost', :safe => false)
146
+
147
+ # Rye follows the standard convention of taking exception to a non-zero
148
+ # exit code by raising a Rye::CommandError. In this case, rye9000.test
149
+ # is not found by the ls command.
150
+ begin
151
+ rbox.ls('rye.test')
152
+ rescue Rye::CommandError => ex
153
+ puts ex.exit_code # => 1
154
+ puts ex.stderr # => ls: rye.test: No such file or directory
155
+ end
156
+
157
+ # The Rye:Rap response objects also give you the STDOUT and STDERR
158
+ # content separately. Here we redirect STDOUT to STDERR, so this
159
+ # will return nothing:
160
+ puts rbox.uname('-a 1>&2').stdout # =>
161
+
162
+ # It all went to STDERR:
163
+ puts rbox.uname('-a 1>&2').stderr # => Darwin ryehost 9.6.0 ...
164
+
165
+ # There were no actual error so the exit code should be 0.
166
+ puts rbox.uname('-a 1>&2').exit_code # => 0
167
+
168
+
169
+
147
170
 
148
171
 
data/lib/rye.rb CHANGED
@@ -1,9 +1,14 @@
1
1
 
2
2
  require 'rubygems' unless defined? Gem
3
3
 
4
+ require 'tempfile'
4
5
  require 'net/ssh'
6
+ require 'net/scp'
5
7
  require 'thread'
6
8
  require 'highline'
9
+ require 'openssl'
10
+ require 'base64'
11
+
7
12
  require 'esc'
8
13
  require 'sys'
9
14
 
@@ -24,7 +29,7 @@ module Rye
24
29
  extend self
25
30
 
26
31
  unless defined?(SYSINFO)
27
- VERSION = 0.3.freeze
32
+ VERSION = 0.4.freeze
28
33
  SYSINFO = SystemInfo.new.freeze
29
34
  end
30
35
 
@@ -37,30 +42,68 @@ module Rye
37
42
  # Accessor for an instance of SystemInfo
38
43
  def sysinfo; SYSINFO; end
39
44
 
40
- class CommandNotFound < RuntimeError; end
41
45
  class NoBoxes < RuntimeError; end
42
46
  class NoHost < RuntimeError; end
43
47
  class NotConnected < RuntimeError; end
48
+ class CommandNotFound < RuntimeError; end
49
+ class CommandError < RuntimeError
50
+ # * +rap+ a Rye::Rap object
51
+ def initialize(rap)
52
+ @rap = rap
53
+ end
54
+ def message
55
+ "(code: %s) %s" % [@rap.exit_code, @rap.stderr.join($/)]
56
+ end
57
+ def stderr; @rap.stderr if @rap; end
58
+ def stdout; @rap.stdout if @rap; end
59
+ def exit_code; @rap.exit_code if @rap; end
60
+ end
44
61
 
45
62
  # Reload Rye dynamically. Useful with irb.
46
63
  # NOTE: does not reload rye.rb.
47
64
  def reload
48
65
  pat = File.join(File.dirname(__FILE__), 'rye')
49
- %w{rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
66
+ %w{key rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
50
67
  end
51
68
 
52
69
  def mutex
53
70
  @@mutex
54
71
  end
55
72
 
73
+ # Looks for private keys in +path+ and returns and Array of paths
74
+ # to the files it fines. Raises an Exception if path does not exist.
75
+ # If path is a file rather than a directory, it will check whether
76
+ # that single file is a private key.
77
+ def find_private_keys(path)
78
+ raise "#{path} does not exist" unless File.exists?(path || '')
79
+ if File.directory?(path)
80
+ files = Dir.entries(path).collect { |file| File.join(path, file) }
81
+ else
82
+ files = [path]
83
+ end
84
+
85
+ files = files.select do |file|
86
+ next if File.directory?(file)
87
+ pk = nil
88
+ begin
89
+ tmp = Rye::Key.from_file(file)
90
+ pk = tmp if tmp.private?
91
+ rescue OpenSSL::PKey::PKeyError
92
+ end
93
+ !pk.nil?
94
+ end
95
+ files || []
96
+ end
56
97
 
98
+
99
+
100
+
57
101
  # Add one or more private keys to the SSH Agent.
58
102
  # * +keys+ one or more file paths to private keys used for passwordless logins.
59
103
  def add_keys(*keys)
60
- keys = [keys].flatten.compact || []
104
+ keys = keys.flatten.compact || []
61
105
  return if keys.empty?
62
- Rye::Box.shell("ssh-add", keys) if keys
63
- Rye::Box.shell("ssh-add") # Add the user's default keys
106
+ Rye.shell("ssh-add", keys) if keys
64
107
  keys
65
108
  end
66
109
 
@@ -73,13 +116,105 @@ module Rye
73
116
  def keys
74
117
  # 2048 76:cb:d7:82:90:92:ad:75:3d:68:6c:a9:21:ca:7b:7f /Users/rye/.ssh/id_rsa (RSA)
75
118
  # 2048 7b:a6:ba:55:b1:10:1d:91:9f:73:3a:aa:0c:d4:88:0e /Users/rye/.ssh/id_dsa (DSA)
76
- keystr = Rye::Box.shell("ssh-add", '-l')
119
+ keystr = Rye.shell("ssh-add", '-l')
77
120
  return nil unless keystr
78
- keystr.split($/).collect do |key|
121
+ keystr.collect do |key|
79
122
  key.split(/\s+/)
80
123
  end
81
124
  end
82
125
 
126
+ def remote_host_keys(*hostnames)
127
+ hostnames = hostnames.flatten.compact || []
128
+ return if hostnames.empty?
129
+ Rye.shell("ssh-keyscan", hostnames)
130
+ end
131
+
132
+ # Takes a command with arguments and returns it in a
133
+ # single String with escaped args and some other stuff.
134
+ #
135
+ # * +cmd+ The shell command name or absolute path.
136
+ # * +args+ an Array of command arguments.
137
+ #
138
+ # The command is searched for in the local PATH (where
139
+ # Rye is running). An exception is raised if it's not
140
+ # found. NOTE: Because this happens locally, you won't
141
+ # want to use this method if the environment is quite
142
+ # different from the remote machine it will be executed
143
+ # on.
144
+ #
145
+ # The command arguments are passed through Escape.shell_command
146
+ # (that means you can't use environment variables or asterisks).
147
+ #
148
+ def prepare_command(cmd, *args)
149
+ args &&= [args].flatten.compact
150
+ cmd = Rye.which(cmd)
151
+ raise CommandNotFound.new(cmd || 'nil') unless cmd
152
+ # Symbols to switches. :l -> -l, :help -> --help
153
+ args.collect! do |a|
154
+ a = "-#{a}" if a.is_a?(Symbol) && a.to_s.size == 1
155
+ a = "--#{a}" if a.is_a?(Symbol)
156
+ a
157
+ end
158
+ Rye.escape(@safe, cmd, *args)
159
+ end
160
+
161
+ # An all ruby implementation of unix "which" command.
162
+ #
163
+ # * +executable+ the name of the executable
164
+ #
165
+ # Returns the absolute path if found in PATH otherwise nil.
166
+ def which(executable)
167
+ return unless executable.is_a?(String)
168
+ #return executable if File.exists?(executable) # SHOULD WORK, MUST TEST
169
+ shortname = File.basename(executable)
170
+ dir = Rye.sysinfo.paths.select do |path| # dir contains all of the
171
+ next unless File.exists? path # occurrences of shortname
172
+ Dir.new(path).entries.member?(shortname) # found in the paths.
173
+ end
174
+ File.join(dir.first, shortname) unless dir.empty? # Return just the first
175
+ end
176
+
177
+ # Execute a local system command (via the shell, not SSH)
178
+ #
179
+ # * +cmd+ the executable path (relative or absolute)
180
+ # * +args+ Array of arguments to be sent to the command. Each element
181
+ # is one argument:. i.e. <tt>['-l', 'some/path']</tt>
182
+ #
183
+ # NOTE: shell is a bit paranoid so it escapes every argument. This means
184
+ # you can only use literal values. That means no asterisks too.
185
+ #
186
+ # Returns a Rye::Rap object containing the
187
+ def shell(cmd, *args)
188
+ args = args.flatten.compact
189
+ cmd = cmd.to_s if cmd.is_a?(Symbol)
190
+ # TODO: allow stdin to be sent to the cmd
191
+ tf = Tempfile.new(cmd)
192
+ cmd = Rye.prepare_command(cmd, args)
193
+ cmd << " 2>#{tf.path}" # Redirect STDERR to file. Works in DOS too.
194
+ # Deal with STDOUT
195
+ handle = IO.popen(cmd, "r")
196
+ stdout = handle.read.chomp
197
+ handle.close
198
+ # Then STDERR
199
+ stderr = File.exists?(tf.path) ? File.read(tf.path) : ''
200
+ tf.delete
201
+ # Create the response object
202
+ rap = Rye::Rap.new(self)
203
+ rap.add_stdout(stdout || '')
204
+ rap.add_stderr(stderr || '')
205
+ rap.exit_code = $?
206
+ rap
207
+ end
208
+
209
+ # Creates a string from +cmd+ and +args+. If +safe+ is true
210
+ # it will send them through Escape.shell_command otherwise
211
+ # it will return them joined by a space character.
212
+ def escape(safe, cmd, *args)
213
+ args = args.flatten.compact || []
214
+ safe ? Escape.shell_command(cmd, *args).to_s : [cmd, args].flatten.compact.join(' ')
215
+ end
216
+
217
+
83
218
  private
84
219
 
85
220
  # Start the SSH Agent locally. This is important
@@ -105,9 +240,8 @@ module Rye
105
240
  #
106
241
  def start_sshagent_environment
107
242
  return if @@agent_env["SSH_AGENT_PID"]
108
-
109
- lines = Rye::Box.shell("ssh-agent", '-s') || ''
110
- lines.split($/).each do |line|
243
+ lines = Rye.shell("ssh-agent", '-s') || []
244
+ lines.each do |line|
111
245
  next unless line.index("echo").nil?
112
246
  line = line.slice(0..(line.index(';')-1))
113
247
  key, value = line.chomp.split( /=/ )
@@ -115,6 +249,8 @@ module Rye
115
249
  end
116
250
  ENV["SSH_AUTH_SOCK"] = @@agent_env["SSH_AUTH_SOCK"]
117
251
  ENV["SSH_AGENT_PID"] = @@agent_env["SSH_AGENT_PID"]
252
+
253
+ Rye.shell("ssh-add") # Add the user's default keys
118
254
  nil
119
255
  end
120
256
 
@@ -129,8 +265,8 @@ module Rye
129
265
  #
130
266
  def end_sshagent_environment
131
267
  pid = @@agent_env["SSH_AGENT_PID"]
132
- Rye::Box.shell("ssh-agent", '-k') || ''
133
- #Rye::Box.shell("kill", ['-9', pid]) if pid
268
+ Rye.shell("ssh-agent", '-k') || []
269
+ #Rye.shell("kill", ['-9', pid]) if pid
134
270
  @@agent_env.clear
135
271
  nil
136
272
  end
@@ -142,6 +278,7 @@ module Rye
142
278
  start_sshagent_environment # Run this now
143
279
  at_exit { end_sshagent_environment } # Run this before Ruby exits
144
280
  }
281
+
145
282
  rescue => ex
146
283
  STDERR.puts "Error initializing the SSH Agent (is OpenSSL installed?):"
147
284
  STDERR.puts ex.message