rush 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # An array of these objects is returned by Rush::Box#processes.
2
+ class Rush::Process
3
+ attr_reader :box, :pid, :uid, :command, :cmdline, :mem, :cpu
4
+
5
+ # params is a hash returned by the system-specific method of looking up the
6
+ # process list.
7
+ def initialize(params, box)
8
+ @box = box
9
+
10
+ @pid = params[:pid].to_i
11
+ @uid = params[:uid].to_i
12
+ @command = params[:command]
13
+ @cmdline = params[:cmdline]
14
+ @mem = params[:rss]
15
+ @cpu = params[:time]
16
+ end
17
+
18
+ def to_s # :nodoc:
19
+ inspect
20
+ end
21
+
22
+ def inspect # :nodoc:
23
+ "Process #{@pid}: #{@cmdline}"
24
+ end
25
+
26
+ # Returns true if the process is currently running.
27
+ def alive?
28
+ box.connection.process_alive(pid)
29
+ end
30
+
31
+ # Terminate the process.
32
+ def kill
33
+ box.connection.kill_process(pid)
34
+ end
35
+
36
+ def self.all
37
+ Rush::Box.new('localhost').processes
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ require 'yaml'
2
+
3
+ # This class it the mirror of Rush::Connection::Local. A Rush::Box which is
4
+ # not localhost has a remote connection, which it can use to convert method
5
+ # calls to text suitable for transmission across the wire.
6
+ #
7
+ # This is an internal class that does not need to be accessed in normal use of
8
+ # the rush shell or library.
9
+ class Rush::Connection::Remote
10
+ attr_reader :host, :tunnel
11
+
12
+ def initialize(host)
13
+ @host = host
14
+ @tunnel = Rush::SshTunnel.new(host)
15
+ end
16
+
17
+ def write_file(full_path, contents)
18
+ transmit(:action => 'write_file', :full_path => full_path, :payload => contents)
19
+ end
20
+
21
+ def file_contents(full_path)
22
+ transmit(:action => 'file_contents', :full_path => full_path)
23
+ end
24
+
25
+ def destroy(full_path)
26
+ transmit(:action => 'destroy', :full_path => full_path)
27
+ end
28
+
29
+ def create_dir(full_path)
30
+ transmit(:action => 'create_dir', :full_path => full_path)
31
+ end
32
+
33
+ def rename(path, name, new_name)
34
+ transmit(:action => 'rename', :path => path, :name => name, :new_name => 'new_name')
35
+ end
36
+
37
+ def copy(src, dst)
38
+ transmit(:action => 'copy', :src => src, :dst => dst)
39
+ end
40
+
41
+ def read_archive(full_path)
42
+ transmit(:action => 'read_archive', :full_path => full_path)
43
+ end
44
+
45
+ def write_archive(archive, dir)
46
+ transmit(:action => 'write_archive', :dir => dir, :payload => archive)
47
+ end
48
+
49
+ def index(base_path, glob)
50
+ transmit(:action => 'index', :base_path => base_path, :glob => glob).split("\n")
51
+ end
52
+
53
+ def stat(full_path)
54
+ YAML.load(transmit(:action => 'stat', :full_path => full_path))
55
+ end
56
+
57
+ def size(full_path)
58
+ transmit(:action => 'size', :full_path => full_path)
59
+ end
60
+
61
+ def processes
62
+ YAML.load(transmit(:action => 'processes'))
63
+ end
64
+
65
+ def process_alive(pid)
66
+ transmit(:action => 'process_alive', :pid => pid)
67
+ end
68
+
69
+ def kill_process(pid)
70
+ transmit(:action => 'kill_process', :pid => pid)
71
+ end
72
+
73
+ class NotAuthorized < Exception; end
74
+ class FailedTransmit < Exception; end
75
+
76
+ # Given a hash of parameters (converted by the method call on the connection
77
+ # object), send it across the wire to the RushServer listening on the other
78
+ # side. Uses http basic auth, with credentials fetched from the Rush::Config.
79
+ def transmit(params)
80
+ tunnel.ensure_tunnel
81
+
82
+ require 'net/http'
83
+
84
+ payload = params.delete(:payload)
85
+
86
+ uri = "/?"
87
+ params.each do |key, value|
88
+ uri += "#{key}=#{value}&"
89
+ end
90
+
91
+ req = Net::HTTP::Post.new(uri)
92
+ req.basic_auth config.credentials_user, config.credentials_password
93
+
94
+ Net::HTTP.start(tunnel.host, tunnel.port) do |http|
95
+ res = http.request(req, payload)
96
+ raise NotAuthorized if res.code == "401"
97
+ raise FailedTransmit if res.code != "200"
98
+ res.body
99
+ end
100
+ end
101
+
102
+ def config
103
+ @config ||= Rush::Config.new
104
+ end
105
+ end
@@ -0,0 +1,58 @@
1
+ # An instance of this class is returned by Rush::Commands#search. It contains
2
+ # both the list of entries which matched the search, as well as the raw line
3
+ # matches. These methods get equivalent functionality to "grep -l" and "grep -h".
4
+ #
5
+ # SearchResults mixes in Rush::Commands so that you can chain multiple searches
6
+ # or do file operations on the resulting entries.
7
+ #
8
+ # Examples:
9
+ #
10
+ # myproj['**/*.rb'].search(/class/).entries.size
11
+ # myproj['**/*.rb'].search(/class/).lines.size
12
+ # myproj['**/*.rb'].search(/class/).copy_to other_dir
13
+ class Rush::SearchResults
14
+ attr_reader :entries, :lines, :entries_with_lines, :pattern
15
+
16
+ # Make a blank container. Track the pattern so that we can colorize the
17
+ # output to show what was matched.
18
+ def initialize(pattern)
19
+ # Duplication of data, but this lets us return everything in the exact
20
+ # order it was received.
21
+ @pattern = pattern
22
+ @entries = []
23
+ @entries_with_lines = {}
24
+ @lines = []
25
+ end
26
+
27
+ # Add a Rush::Entry and the array of string matches.
28
+ def add(entry, lines)
29
+ # this assumes that entry is unique
30
+ @entries << entry
31
+ @entries_with_lines[entry] = lines
32
+ @lines += lines
33
+ end
34
+
35
+ include Rush::Commands
36
+
37
+ def each(&block)
38
+ @entries.each(&block)
39
+ end
40
+
41
+ include Enumerable
42
+
43
+ def colorize(line)
44
+ lowlight + line.gsub(/(#{pattern.source})/, "#{hilight}\\1#{lowlight}") + normal
45
+ end
46
+
47
+ def hilight
48
+ "\e[34;1m"
49
+ end
50
+
51
+ def lowlight
52
+ "\e[37;2m"
53
+ end
54
+
55
+ def normal
56
+ "\e[0m"
57
+ end
58
+ end
@@ -0,0 +1,81 @@
1
+ require 'rubygems'
2
+ require 'mongrel'
3
+ require 'base64'
4
+
5
+ # Mongrel handler that translates the incoming HTTP request into a
6
+ # Rush::Connection::Local call. The results are sent back across the wire to
7
+ # be decoded by Rush::Connection::Remote on the other side.
8
+ class RushHandler < Mongrel::HttpHandler
9
+ def process(request, response)
10
+ params = {}
11
+ request.params['QUERY_STRING'].split("?").last.split("&").each do |tuple|
12
+ key, value = tuple.split("=")
13
+ params[key.to_sym] = value
14
+ end
15
+
16
+ unless authorize(request.params['HTTP_AUTHORIZATION'])
17
+ response.start(401) do |head, out|
18
+ end
19
+ else
20
+ payload = request.body.read
21
+
22
+ without_action = params
23
+ without_action.delete(params[:action])
24
+ printf "%-20s", params[:action]
25
+ print without_action.inspect
26
+ print " + #{payload.size} bytes of payload" if payload.size > 0
27
+ puts
28
+
29
+ params[:payload] = payload
30
+ result = box.connection.receive(params)
31
+
32
+ response.start(200) do |head, out|
33
+ out.write result
34
+ end
35
+ end
36
+ end
37
+
38
+ def authorize(auth)
39
+ unless m = auth.match(/^Basic (.+)$/)
40
+ puts "Request with no authorization data"
41
+ return false
42
+ end
43
+
44
+ decoded = Base64.decode64(m[1])
45
+ user, password = decoded.split(':', 2)
46
+
47
+ if user.nil? or user.length == 0 or password.nil? or password.length == 0
48
+ puts "Malformed user or password"
49
+ return false
50
+ end
51
+
52
+ if password == config.passwords[user]
53
+ return true
54
+ else
55
+ puts "Access denied to #{user}"
56
+ return false
57
+ end
58
+ end
59
+
60
+ def box
61
+ @box ||= Rush::Box.new('localhost')
62
+ end
63
+
64
+ def config
65
+ @config ||= Rush::Config.new
66
+ end
67
+ end
68
+
69
+ # A container class to run the Mongrel server for rushd.
70
+ class RushServer
71
+ def run
72
+ host = "127.0.0.1"
73
+ port = Rush::Config::DefaultPort
74
+
75
+ puts "rushd listening on #{host}:#{port}"
76
+
77
+ h = Mongrel::HttpServer.new(host, port)
78
+ h.register("/", RushHandler.new)
79
+ h.run.join
80
+ end
81
+ end
@@ -0,0 +1,123 @@
1
+ require 'readline'
2
+
3
+ # Rush::Shell is used to create an interactive shell. It is invoked by the rush binary.
4
+ module Rush
5
+ class Shell
6
+ # Set up the user's environment, including a pure binding into which
7
+ # env.rb and commands.rb are mixed.
8
+ def initialize
9
+ root = Rush::Dir.new('/')
10
+ home = Rush::Dir.new(ENV['HOME'])
11
+ pwd = Rush::Dir.new(ENV['PWD'])
12
+
13
+ @pure_binding = Proc.new { }
14
+ $last_res = nil
15
+
16
+ @config = Rush::Config.new
17
+
18
+ @config.load_history.each do |item|
19
+ Readline::HISTORY.push(item)
20
+ end
21
+
22
+ Readline.basic_word_break_characters = ""
23
+ Readline.completion_append_character = nil
24
+ Readline.completion_proc = completion_proc
25
+
26
+ eval @config.load_env, @pure_binding
27
+
28
+ eval "def processes; Rush::Box.new('localhost').processes; end", @pure_binding
29
+
30
+ commands = @config.load_commands
31
+ Rush::Dir.class_eval commands
32
+ Array.class_eval commands
33
+ end
34
+
35
+ # Run the interactive shell using readline.
36
+ def run
37
+ loop do
38
+ cmd = Readline.readline('rush> ')
39
+
40
+ finish if cmd.nil?
41
+ next if cmd == ""
42
+ Readline::HISTORY.push(cmd)
43
+
44
+ begin
45
+ res = eval(cmd, @pure_binding)
46
+ $last_res = res
47
+ eval("_ = $last_res", @pure_binding)
48
+ print_result res
49
+ rescue Exception => e
50
+ puts "Exception #{e.class}: #{e}"
51
+ e.backtrace.each do |t|
52
+ puts " #{::File.expand_path(t)}"
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Save history to ~/.rush/history when the shell exists.
59
+ def finish
60
+ @config.save_history(Readline::HISTORY.to_a)
61
+ puts
62
+ exit
63
+ end
64
+
65
+ # Nice printing of different return types, particularly Rush::SearchResults.
66
+ def print_result(res)
67
+ if res.kind_of? String
68
+ puts res
69
+ elsif res.kind_of? Array
70
+ res.each do |item|
71
+ puts item
72
+ end
73
+ elsif res.kind_of? Rush::SearchResults
74
+ widest = res.entries.map { |k| k.full_path.length }.max
75
+ res.entries_with_lines.each do |entry, lines|
76
+ print entry.full_path
77
+ print ' ' * (widest - entry.full_path.length + 2)
78
+ print "=> "
79
+ print res.colorize(lines.first.strip.head(30))
80
+ print "..." if lines.first.strip.length > 30
81
+ if lines.size > 1
82
+ print " (plus #{lines.size - 1} more matches)"
83
+ end
84
+ print "\n"
85
+ end
86
+ puts "#{res.entries.size} matching files with #{res.lines.size} matching lines"
87
+ else
88
+ puts "=> #{res.inspect}"
89
+ end
90
+ end
91
+
92
+ def path_parts(input) # :nodoc:
93
+ input.match(/^(.+)\[(['"])([^\]]+)$/).to_a.slice(1, 3) rescue [ nil, nil, nil ]
94
+ end
95
+
96
+ # Try to do tab completion on dir square brackets accessors.
97
+ #
98
+ # Example:
99
+ #
100
+ # dir['subd # presing tab here will produce dir['subdir/ if subdir exists
101
+ #
102
+ # This isn't that cool yet, because it can't do multiple levels of subdirs.
103
+ # It does work remotely, though, which is pretty sweet.
104
+ def completion_proc
105
+ proc do |input|
106
+ possible_var, quote, partial_path = path_parts(input)
107
+ if possible_var and possible_var.match(/^[a-z0-9_]+$/)
108
+ full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil
109
+ box = eval("#{possible_var}.box", @pure_binding) rescue nil
110
+ if full_path and box
111
+ dir = Rush::Dir.new(full_path, box)
112
+ return dir.entries.select do |e|
113
+ e.name.match(/^#{partial_path}/)
114
+ end.map do |e|
115
+ possible_var + '[' + quote + e.name + (e.dir? ? "/" : "")
116
+ end
117
+ end
118
+ end
119
+ nil
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,113 @@
1
+ # Internal class for managing an ssh tunnel, across which relatively insecure
2
+ # HTTP commands can be sent by Rush::Connection::Remote.
3
+ class Rush::SshTunnel
4
+ def initialize(real_host)
5
+ @real_host = real_host
6
+ end
7
+
8
+ def host
9
+ 'localhost'
10
+ end
11
+
12
+ def port
13
+ @port
14
+ end
15
+
16
+ def ensure_tunnel
17
+ return if @port and tunnel_alive?
18
+
19
+ @port = config.tunnels[@real_host]
20
+
21
+ if !@port or !tunnel_alive?
22
+ setup_everything
23
+ end
24
+ end
25
+
26
+ def setup_everything
27
+ display "Connecting to #{@real_host}..."
28
+ push_credentials
29
+ launch_rushd
30
+ establish_tunnel
31
+ end
32
+
33
+ def push_credentials
34
+ display "Pushing credentials"
35
+ config.ensure_credentials_exist
36
+ ssh_append_to_credentials(config.credentials_file.contents.strip)
37
+ end
38
+
39
+ def ssh_append_to_credentials(string)
40
+ # the following horror is exactly why rush is needed
41
+ passwords_file = "~/.rush/passwords"
42
+ string = "'#{string}'"
43
+ ssh "M=`grep #{string} #{passwords_file} | wc -l`; if [ $M = 0 ]; then echo #{string} >> #{passwords_file}; fi"
44
+ end
45
+
46
+ def launch_rushd
47
+ display "Launching rushd"
48
+ ssh("if [ `ps aux | grep rushd | grep -v grep | wc -l` -ge 1 ]; then exit; fi; rushd > /dev/null 2>&1 &")
49
+ end
50
+
51
+ def establish_tunnel
52
+ display "Establishing ssh tunnel"
53
+ @port = next_available_port
54
+
55
+ make_ssh_tunnel
56
+
57
+ tunnels = config.tunnels
58
+ tunnels[@real_host] = @port
59
+ config.save_tunnels tunnels
60
+
61
+ sleep 0.5
62
+ end
63
+
64
+ def tunnel_options
65
+ {
66
+ :local_port => @port,
67
+ :remote_port => Rush::Config::DefaultPort,
68
+ :ssh_host => @real_host,
69
+ :stall_command => "sleep 9000"
70
+ }
71
+ end
72
+
73
+ def tunnel_alive?
74
+ `#{tunnel_count_command}`.to_i > 0
75
+ end
76
+
77
+ def tunnel_count_command
78
+ "ps x | grep '#{ssh_tunnel_command_without_stall}' | grep -v grep | wc -l"
79
+ end
80
+
81
+ class SshFailed < Exception; end
82
+ class NoPortSelectedYet < Exception; end
83
+
84
+ def ssh(command)
85
+ raise SshFailed unless system("ssh #{@real_host} '#{command}'")
86
+ end
87
+
88
+ def make_ssh_tunnel
89
+ raise SshFailed unless system(ssh_tunnel_command)
90
+ end
91
+
92
+ def ssh_tunnel_command_without_stall
93
+ options = tunnel_options
94
+ raise NoPortSelectedYet unless options[:local_port]
95
+ "ssh -f -L #{options[:local_port]}:127.0.0.1:#{options[:remote_port]} #{options[:ssh_host]}"
96
+ end
97
+
98
+ def ssh_tunnel_command
99
+ ssh_tunnel_command_without_stall + " \"#{tunnel_options[:stall_command]}\""
100
+ end
101
+
102
+ def next_available_port
103
+ (config.tunnels.values.max || Rush::Config::DefaultPort) + 1
104
+ end
105
+
106
+ def config
107
+ @config ||= Rush::Config.new
108
+ end
109
+
110
+ def display(msg)
111
+ puts msg
112
+ end
113
+ end