Module: Rye

Extended by:
Rye
Included in:
Rye
Defined in:
lib/rye.rb,
lib/rye/box.rb,
lib/rye/cmd.rb,
lib/rye/key.rb,
lib/rye/rap.rb,
lib/rye/set.rb

Overview

Rye

Safely run remote commands via SSH in Ruby.

Rye is similar to Rush but everything happens over SSH (no HTTP daemon) and the default settings are less dangerous (for safety). For example, file globs and the “rm” command are disabled so unless otherwise specified, you can’t do this: rbox.rm('/etc/*/').

However, you can do this:

rset = Rye::Set.new(“dev-machines”) rset.add_boxes(‘host1’, ‘host2’, ‘host3’, ‘host4’) rset.ps(‘aux’)

  • See bin/try for a bunch of working examples.

  • See Rye::Box#initialize for info about disabling safe-mode.

– The following posts were really helpful when I first wrote Rye: www.nofluffjuststuff.com/blog/david_bock/2008/10/ruby_s_closure_cleanup_idiom_and_net_ssh.html groups.google.com/group/ruby-talk-google/browse_thread/thread/674a6f6de15ceb49?pli=1 paste.lisp.org/display/6912 ++

Defined Under Namespace

Modules: Cmd Classes: Box, CommandError, CommandNotFound, Key, NoBoxes, NoHost, NoPassword, NoPty, NotConnected, Rap, RyeError, Set

Constant Summary collapse

VERSION =
"0.8.6".freeze
SYSINFO =
SysInfo.new.freeze
@@agent_env =

holds ssh-agent env vars

Hash.new
@@mutex =

for synchronizing threads

Mutex.new

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.sysinfoObject

Accessor for an instance of SystemInfo



54
# File 'lib/rye.rb', line 54

def Rye.sysinfo; SYSINFO; end

Instance Method Details

#add_keys(*keys) ⇒ Object

Add one or more private keys to the SSH Agent.

  • keys one or more file paths to private keys used for passwordless logins.



122
123
124
125
126
# File 'lib/rye.rb', line 122

def add_keys(*keys)
  keys = keys.flatten.compact || []
  return if keys.empty?
  Rye.shell("ssh-add", keys)
end

#escape(safe, cmd, *args) ⇒ Object

Creates a string from cmd and args. If safe is true it will send them through Escape.shell_command otherwise it will return them joined by a space character.



232
233
234
235
# File 'lib/rye.rb', line 232

def escape(safe, cmd, *args)
  args = args.flatten.compact || []
  safe ? Escape.shell_command(cmd, *args).to_s : [cmd, args].flatten.compact.join(' ')
end

#find_private_keys(path) ⇒ Object

Looks for private keys in path and returns and Array of paths to the files it fines. Raises an Exception if path does not exist. If path is a file rather than a directory, it will check whether that single file is a private key.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/rye.rb', line 99

def find_private_keys(path)
  raise "#{path} does not exist" unless File.exists?(path || '')
  if File.directory?(path)
    files = Dir.entries(path).collect { |file| File.join(path, file) }
  else
    files = [path]
  end
  
  files = files.select do |file|
    next if File.directory?(file)
    pk = nil
    begin
      tmp = Rye::Key.from_file(file) 
      pk = tmp if tmp.private?
    rescue OpenSSL::PKey::PKeyError
    end
    !pk.nil?
  end
  files || []
end

#keysObject

Returns an Array of info about the currently available SSH keys, as provided by the SSH Agent. See Rye.start_sshagent_environment

Returns: [[bits, finger-print, file-path], …]



134
135
136
137
138
139
140
141
142
143
# File 'lib/rye.rb', line 134

def keys
  # 2048 76:cb:d7:82:90:92:ad:75:3d:68:6c:a9:21:ca:7b:7f /Users/rye/.ssh/id_rsa (RSA)
  # 2048 7b:a6:ba:55:b1:10:1d:91:9f:73:3a:aa:0c:d4:88:0e /Users/rye/.ssh/id_dsa (DSA)
  #keystr = Rye.shell("ssh-add", '-l')
  #return nil unless keystr
  #keystr.collect do |key|
  #  key.split(/\s+/)
  #end
  Dir.glob(File.join(Rye.sysinfo.home, '.ssh', 'id_*sa'))
end

#mutexObject



91
92
93
# File 'lib/rye.rb', line 91

def mutex
  @@mutex
end

#prepare_command(cmd, *args) ⇒ Object

Takes a command with arguments and returns it in a single String with escaped args and some other stuff.

  • cmd The shell command name or absolute path.

  • args an Array of command arguments.

The command is searched for in the local PATH (where Rye is running). An exception is raised if it’s not found. NOTE: Because this happens locally, you won’t want to use this method if the environment is quite different from the remote machine it will be executed on.

The command arguments are passed through Escape.shell_command (that means you can’t use environment variables or asterisks).

Raises:



167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/rye.rb', line 167

def prepare_command(cmd, *args)
  args &&= [args].flatten.compact
  found_cmd = Rye.which(cmd)
  raise CommandNotFound.new(cmd || '[unknown]') unless found_cmd
  # Symbols to switches. :l -> -l, :help -> --help
  args.collect! do |a|
    a = "-#{a}" if a.is_a?(Symbol) && a.to_s.size == 1
    a = "--#{a}" if a.is_a?(Symbol)
    a
  end
  Rye.escape(@safe, found_cmd, *args)
end

#reloadObject

Reload Rye dynamically. Useful with irb. NOTE: does not reload rye.rb.



86
87
88
89
# File 'lib/rye.rb', line 86

def reload
  pat = File.join(File.dirname(__FILE__), 'rye')
  %w{key rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
end

#remote_host_keys(*hostnames) ⇒ Object



145
146
147
148
149
# File 'lib/rye.rb', line 145

def remote_host_keys(*hostnames)
  hostnames = hostnames.flatten.compact || []
  return if hostnames.empty?
  Rye.shell("ssh-keyscan", hostnames)
end

#shell(cmd, *args) ⇒ Object

Execute a local system command (via the shell, not SSH)

  • cmd the executable path (relative or absolute)

  • args Array of arguments to be sent to the command. Each element

is one argument:. i.e. ['-l', 'some/path']

NOTE: shell is a bit paranoid so it escapes every argument. This means you can only use literal values. That means no asterisks too.

Returns a Rye::Rap object.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/rye.rb', line 207

def shell(cmd, *args)
  args = args.flatten.compact
  cmd = cmd.to_s if cmd.is_a?(Symbol)
  # TODO: allow stdin to be sent to the cmd
  tf = Tempfile.new(cmd)
  cmd = Rye.prepare_command(cmd, args)
  cmd << " 2>#{tf.path}" # Redirect STDERR to file. Works in DOS too.
  # Deal with STDOUT
  handle = IO.popen(cmd, "r")
  stdout = handle.read.chomp
  handle.close
  # Then STDERR
  stderr = File.exists?(tf.path) ? File.read(tf.path) : ''
  tf.delete
  # Create the response object
  rap = Rye::Rap.new(self)
  rap.add_stdout(stdout || '')
  rap.add_stderr(stderr || '')
  rap.add_exit_code($?)
  rap
end

#sshagent_infoObject



237
238
239
# File 'lib/rye.rb', line 237

def sshagent_info
  @@agent_env
end

#strand(len = 8, safe = true) ⇒ Object

Generates a string of random alphanumeric characters.

  • len is the length, an Integer. Default: 8

  • safe in safe-mode, ambiguous characters are removed (default: true):

    i l o 1 0
    


253
254
255
256
257
258
259
# File 'lib/rye.rb', line 253

def strand( len=8, safe=true )
   chars = ("a".."z").to_a + ("0".."9").to_a
   chars.delete_if { |v| %w(i l o 1 0).member?(v) } if safe
   str = ""
   1.upto(len) { |i| str << chars[rand(chars.size-1)] }
   str
end

#sysinfoObject

Accessor for an instance of SystemInfo



57
# File 'lib/rye.rb', line 57

def sysinfo; SYSINFO;  end

#which(executable) ⇒ Object

An all ruby implementation of unix “which” command.

  • executable the name of the executable

Returns the absolute path if found in PATH otherwise nil.



185
186
187
188
189
190
191
192
193
194
# File 'lib/rye.rb', line 185

def which(executable)
  return unless executable.is_a?(String)
  #return executable if File.exists?(executable) # SHOULD WORK, MUST TEST
  shortname = File.basename(executable)
  dir = Rye.sysinfo.paths.select do |path|    # dir contains all of the 
    next unless File.exists? path             # occurrences of shortname  
    Dir.new(path).entries.member?(shortname)  # found in the paths. 
  end
  File.join(dir.first, shortname) unless dir.empty? # Return just the first
end

#without_indent(str) ⇒ Object

Returns str with the leading indentation removed. Stolen from github.com/mynyml/unindent/ because it was better.



243
244
245
246
# File 'lib/rye.rb', line 243

def without_indent(str)
  indent = str.split($/).each {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min
  str.gsub(/^[[:blank:]]{#{indent}}/, '')
end