aleksi-rush 0.6.6
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.
- data/README.rdoc +89 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/bin/rush +13 -0
- data/bin/rushd +7 -0
- data/lib/rush.rb +87 -0
- data/lib/rush/access.rb +130 -0
- data/lib/rush/array_ext.rb +19 -0
- data/lib/rush/box.rb +115 -0
- data/lib/rush/commands.rb +55 -0
- data/lib/rush/config.rb +154 -0
- data/lib/rush/dir.rb +160 -0
- data/lib/rush/embeddable_shell.rb +26 -0
- data/lib/rush/entry.rb +189 -0
- data/lib/rush/exceptions.rb +39 -0
- data/lib/rush/file.rb +85 -0
- data/lib/rush/find_by.rb +39 -0
- data/lib/rush/fixnum_ext.rb +18 -0
- data/lib/rush/head_tail.rb +11 -0
- data/lib/rush/local.rb +398 -0
- data/lib/rush/process.rb +59 -0
- data/lib/rush/process_set.rb +62 -0
- data/lib/rush/remote.rb +158 -0
- data/lib/rush/search_results.rb +58 -0
- data/lib/rush/server.rb +117 -0
- data/lib/rush/shell.rb +187 -0
- data/lib/rush/ssh_tunnel.rb +122 -0
- data/lib/rush/string_ext.rb +3 -0
- data/spec/access_spec.rb +134 -0
- data/spec/array_ext_spec.rb +15 -0
- data/spec/base.rb +24 -0
- data/spec/box_spec.rb +80 -0
- data/spec/commands_spec.rb +47 -0
- data/spec/config_spec.rb +108 -0
- data/spec/dir_spec.rb +164 -0
- data/spec/embeddable_shell_spec.rb +17 -0
- data/spec/entry_spec.rb +133 -0
- data/spec/file_spec.rb +83 -0
- data/spec/find_by_spec.rb +58 -0
- data/spec/fixnum_ext_spec.rb +19 -0
- data/spec/local_spec.rb +364 -0
- data/spec/process_set_spec.rb +50 -0
- data/spec/process_spec.rb +73 -0
- data/spec/remote_spec.rb +140 -0
- data/spec/rush_spec.rb +28 -0
- data/spec/search_results_spec.rb +44 -0
- data/spec/shell_spec.rb +23 -0
- data/spec/ssh_tunnel_spec.rb +122 -0
- data/spec/string_ext_spec.rb +23 -0
- metadata +142 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rush
|
2
|
+
# Base class for all rush exceptions.
|
3
|
+
class Exception < ::RuntimeError; end
|
4
|
+
|
5
|
+
# Client was not authorized by remote server; check credentials.
|
6
|
+
class NotAuthorized < Exception; end
|
7
|
+
|
8
|
+
# rushd is not running on the remote box.
|
9
|
+
class RushdNotRunning < Exception; end
|
10
|
+
|
11
|
+
# An unrecognized status code was returned by rushd.
|
12
|
+
class FailedTransmit < Exception; end
|
13
|
+
|
14
|
+
# The entry (file or dir) referenced does not exist. Message is the entry's full path.
|
15
|
+
class DoesNotExist < Exception; end
|
16
|
+
|
17
|
+
# The bash command had a non-zero return value. Message is stderr. stdout is also available.
|
18
|
+
class BashFailed < Exception
|
19
|
+
def stderr; to_s; end
|
20
|
+
attr_reader :stdout
|
21
|
+
|
22
|
+
def initialize(err, out)
|
23
|
+
super(err)
|
24
|
+
@stdout = out
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# There's already an entry by the given name in the given dir.
|
29
|
+
class NameAlreadyExists < Exception; end
|
30
|
+
|
31
|
+
# The name cannot contain a slash; use two operations, rename and then move, instead.
|
32
|
+
class NameCannotContainSlash < Exception; end
|
33
|
+
|
34
|
+
# You cannot move or copy entries to a path that is not a dir (should end with trailing slash).
|
35
|
+
class NotADir < Exception; end
|
36
|
+
|
37
|
+
# A user or permission value specified to set access was not valid.
|
38
|
+
class BadAccessSpecifier < Exception; end
|
39
|
+
end
|
data/lib/rush/file.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Files are a subclass of Rush::Entry. Most of the file-specific operations
|
2
|
+
# relate to manipulating the file's contents, like search and replace.
|
3
|
+
class Rush::File < Rush::Entry
|
4
|
+
def dir?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
# Create a blank file.
|
9
|
+
def create
|
10
|
+
write('')
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Size in bytes on disk.
|
15
|
+
def size
|
16
|
+
stat[:size]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raw contents of the file. For non-text files, you probably want to avoid
|
20
|
+
# printing this on the screen.
|
21
|
+
def contents
|
22
|
+
connection.file_contents(full_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias :read :contents
|
26
|
+
|
27
|
+
# Write to the file, overwriting whatever was already in it.
|
28
|
+
#
|
29
|
+
# Example: file.write "hello, world\n"
|
30
|
+
def write(new_contents)
|
31
|
+
connection.write_file(full_path, new_contents)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Append new contents to the end of the file, keeping what was in it.
|
35
|
+
def append(contents)
|
36
|
+
connection.append_to_file(full_path, contents)
|
37
|
+
end
|
38
|
+
alias_method :<<, :append
|
39
|
+
|
40
|
+
# Return an array of lines from the file, similar to stdlib's File#readlines.
|
41
|
+
def lines
|
42
|
+
contents.split("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Search the file's for a regular expression. Returns nil if no match, or
|
46
|
+
# each of the matching lines in its entirety.
|
47
|
+
#
|
48
|
+
# Example: box['/etc/hosts'].search(/localhost/) # -> [ "127.0.0.1 localhost\n", "::1 localhost\n" ]
|
49
|
+
def search(pattern)
|
50
|
+
matching_lines = lines.select { |line| line.match(pattern) }
|
51
|
+
matching_lines.size == 0 ? nil : matching_lines
|
52
|
+
end
|
53
|
+
|
54
|
+
# Search-and-replace file contents.
|
55
|
+
#
|
56
|
+
# Example: box['/etc/hosts'].replace_contents!(/localhost/, 'local.host')
|
57
|
+
def replace_contents!(pattern, replace_with)
|
58
|
+
write contents.gsub(pattern, replace_with)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return the file's contents, or if it doesn't exist, a blank string.
|
62
|
+
def contents_or_blank
|
63
|
+
contents
|
64
|
+
rescue Rush::DoesNotExist
|
65
|
+
""
|
66
|
+
end
|
67
|
+
|
68
|
+
# Count the number of lines in the file.
|
69
|
+
def line_count
|
70
|
+
lines.size
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return an array of lines, or an empty array if the file does not exist.
|
74
|
+
def lines_or_empty
|
75
|
+
lines
|
76
|
+
rescue Rush::DoesNotExist
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
|
80
|
+
include Rush::Commands
|
81
|
+
|
82
|
+
def entries
|
83
|
+
[ self ]
|
84
|
+
end
|
85
|
+
end
|
data/lib/rush/find_by.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Generic find_by (returns first match) and find_all_by (returns all matches)
|
2
|
+
# against arrays.
|
3
|
+
#
|
4
|
+
# Examples:
|
5
|
+
#
|
6
|
+
# processes.find_by_pid(::Process.pid)
|
7
|
+
# processes.find_all_by_cmdline(/mongrel_rails/)
|
8
|
+
#
|
9
|
+
module Rush::FindBy
|
10
|
+
def method_missing(meth, *args)
|
11
|
+
if m = meth.to_s.match(/^find_by_([a-z_]+)$/)
|
12
|
+
find_by(m[1], args.first)
|
13
|
+
elsif m = meth.to_s.match(/^find_all_by_([a-z_]+)$/)
|
14
|
+
find_all_by(m[1], args.first)
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by(field, arg)
|
21
|
+
detect do |item|
|
22
|
+
item.respond_to?(field) and compare_or_match(item.send(field), arg)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_all_by(field, arg)
|
27
|
+
select do |item|
|
28
|
+
item.respond_to?(field) and compare_or_match(item.send(field), arg)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def compare_or_match(value, against)
|
33
|
+
if against.class == Regexp
|
34
|
+
value.match(against) ? true : false
|
35
|
+
else
|
36
|
+
value == against
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Integer extensions for file and dir sizes (returned in bytes).
|
2
|
+
#
|
3
|
+
# Example:
|
4
|
+
#
|
5
|
+
# box['/assets/'].files_flattened.select { |f| f.size > 10.mb }
|
6
|
+
class Fixnum
|
7
|
+
def kb
|
8
|
+
self * 1024
|
9
|
+
end
|
10
|
+
|
11
|
+
def mb
|
12
|
+
kb * 1024
|
13
|
+
end
|
14
|
+
|
15
|
+
def gb
|
16
|
+
mb * 1024
|
17
|
+
end
|
18
|
+
end
|
data/lib/rush/local.rb
ADDED
@@ -0,0 +1,398 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
# Rush::Box uses a connection object to execute all rush commands. If the box
|
6
|
+
# is local, Rush::Connection::Local is created. The local connection is the
|
7
|
+
# heart of rush's internals. (Users of the rush shell or library need never
|
8
|
+
# access the connection object directly, so the docs herein are intended for
|
9
|
+
# developers wishing to modify rush.)
|
10
|
+
#
|
11
|
+
# The local connection has a series of methods which do the actual work of
|
12
|
+
# modifying files, getting process lists, and so on. RushServer creates a
|
13
|
+
# local connection to handle incoming requests; the translation from a raw hash
|
14
|
+
# of parameters to an executed method is handled by
|
15
|
+
# Rush::Connection::Local#receive.
|
16
|
+
class Rush::Connection::Local
|
17
|
+
# Write raw bytes to a file.
|
18
|
+
def write_file(full_path, contents)
|
19
|
+
::File.open(full_path, 'w') do |f|
|
20
|
+
f.write contents
|
21
|
+
end
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# Append contents to a file
|
26
|
+
def append_to_file(full_path, contents)
|
27
|
+
::File.open(full_path, 'a') do |f|
|
28
|
+
f.write contents
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Read raw bytes from a file.
|
34
|
+
def file_contents(full_path)
|
35
|
+
::File.read(full_path)
|
36
|
+
rescue Errno::ENOENT
|
37
|
+
raise Rush::DoesNotExist, full_path
|
38
|
+
end
|
39
|
+
|
40
|
+
# Destroy a file or dir.
|
41
|
+
def destroy(full_path)
|
42
|
+
raise "No." if full_path == '/'
|
43
|
+
FileUtils.rm_rf(full_path)
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Purge the contents of a dir.
|
48
|
+
def purge(full_path)
|
49
|
+
raise "No." if full_path == '/'
|
50
|
+
Dir.chdir(full_path) do
|
51
|
+
all = Dir.glob("*", File::FNM_DOTMATCH).reject { |f| f == '.' or f == '..' }
|
52
|
+
FileUtils.rm_rf all
|
53
|
+
end
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a dir.
|
58
|
+
def create_dir(full_path)
|
59
|
+
FileUtils.mkdir_p(full_path)
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Rename an entry within a dir.
|
64
|
+
def rename(path, name, new_name, force)
|
65
|
+
raise(Rush::NameCannotContainSlash, "#{path} rename #{name} to #{new_name}") if new_name.match(/\//)
|
66
|
+
old_full_path = "#{path}/#{name}"
|
67
|
+
new_full_path = "#{path}/#{new_name}"
|
68
|
+
raise(Rush::NameAlreadyExists, "#{path} rename #{name} to #{new_name}") if (::File.exists?(new_full_path) and not force)
|
69
|
+
FileUtils.mv(old_full_path, new_full_path, :force => force)
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
# Copy ane entry from one path to another.
|
74
|
+
def copy(src, dst, force)
|
75
|
+
FileUtils.cp_r(src, dst, :remove_destination => force)
|
76
|
+
true
|
77
|
+
rescue Errno::ENOENT
|
78
|
+
raise Rush::DoesNotExist, File.dirname(dst)
|
79
|
+
rescue RuntimeError
|
80
|
+
raise Rush::DoesNotExist, src
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create an in-memory archive (tar) of a file or dir, which can be
|
84
|
+
# transmitted to another server for a copy or move. Note that archive
|
85
|
+
# operations have the dir name implicit in the archive.
|
86
|
+
def read_archive(full_path)
|
87
|
+
`cd #{Rush.quote(::File.dirname(full_path))}; tar c #{Rush.quote(::File.basename(full_path))}`
|
88
|
+
end
|
89
|
+
|
90
|
+
# Extract an in-memory archive to a dir.
|
91
|
+
def write_archive(archive, dir, force)
|
92
|
+
IO.popen("cd #{Rush::quote(dir)}; tar x #{'--keep-old-files' unless force}", "w") do |p|
|
93
|
+
p.write archive
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get an index of files from the given path with the glob. Could return
|
98
|
+
# nested values if the glob contains a doubleglob. The return value is an
|
99
|
+
# array of full paths, with directories listed first.
|
100
|
+
def index(base_path, glob)
|
101
|
+
glob = '*' if glob == '' or glob.nil?
|
102
|
+
dirs = []
|
103
|
+
files = []
|
104
|
+
::Dir.chdir(base_path) do
|
105
|
+
::Dir.glob(glob).each do |fname|
|
106
|
+
if ::File.directory?(fname)
|
107
|
+
dirs << fname + '/'
|
108
|
+
else
|
109
|
+
files << fname
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
dirs.sort + files.sort
|
114
|
+
rescue Errno::ENOENT
|
115
|
+
raise Rush::DoesNotExist, base_path
|
116
|
+
end
|
117
|
+
|
118
|
+
# Fetch stats (size, ctime, etc) on an entry. Size will not be accurate for dirs.
|
119
|
+
def stat(full_path)
|
120
|
+
s = ::File.stat(full_path)
|
121
|
+
{
|
122
|
+
:size => s.size,
|
123
|
+
:ctime => s.ctime,
|
124
|
+
:atime => s.atime,
|
125
|
+
:mtime => s.mtime,
|
126
|
+
:mode => s.mode
|
127
|
+
}
|
128
|
+
rescue Errno::ENOENT
|
129
|
+
raise Rush::DoesNotExist, full_path
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_access(full_path, access)
|
133
|
+
access.apply(full_path)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Fetch the size of a dir, since a standard file stat does not include the
|
137
|
+
# size of the contents.
|
138
|
+
def size(full_path)
|
139
|
+
`du -sb #{Rush.quote(full_path)}`.match(/(\d+)/)[1].to_i
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get the list of processes as an array of hashes.
|
143
|
+
def processes
|
144
|
+
if ::File.directory? "/proc"
|
145
|
+
resolve_unix_uids(linux_processes)
|
146
|
+
elsif ::File.directory? "C:/WINDOWS"
|
147
|
+
windows_processes
|
148
|
+
else
|
149
|
+
os_x_processes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Process list on Linux using /proc.
|
154
|
+
def linux_processes
|
155
|
+
list = []
|
156
|
+
::Dir["/proc/*/stat"].select { |file| file =~ /\/proc\/\d+\// }.each do |file|
|
157
|
+
begin
|
158
|
+
list << read_proc_file(file)
|
159
|
+
rescue
|
160
|
+
# process died between the dir listing and accessing the file
|
161
|
+
end
|
162
|
+
end
|
163
|
+
list
|
164
|
+
end
|
165
|
+
|
166
|
+
def resolve_unix_uids(list)
|
167
|
+
@uid_map = {} # reset the cache between uid resolutions.
|
168
|
+
list.each do |process|
|
169
|
+
process[:user] = resolve_unix_uid_to_user(process[:uid])
|
170
|
+
end
|
171
|
+
list
|
172
|
+
end
|
173
|
+
|
174
|
+
# resolve uid to user
|
175
|
+
def resolve_unix_uid_to_user(uid)
|
176
|
+
require 'etc'
|
177
|
+
|
178
|
+
@uid_map ||= {}
|
179
|
+
uid = uid.to_i
|
180
|
+
|
181
|
+
return @uid_map[uid] if !@uid_map[uid].nil?
|
182
|
+
|
183
|
+
begin
|
184
|
+
record = Etc.getpwuid(uid)
|
185
|
+
rescue ArgumentError
|
186
|
+
return nil
|
187
|
+
end
|
188
|
+
|
189
|
+
@uid_map[uid] = record.name
|
190
|
+
@uid_map[uid]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Read a single file in /proc and store the parsed values in a hash suitable
|
194
|
+
# for use in the Rush::Process#new.
|
195
|
+
def read_proc_file(file)
|
196
|
+
data = ::File.read(file).split(" ")
|
197
|
+
uid = ::File.stat(file).uid
|
198
|
+
pid = data[0]
|
199
|
+
command = data[1].match(/^\((.*)\)$/)[1]
|
200
|
+
cmdline = ::File.read("/proc/#{pid}/cmdline").gsub(/\0/, ' ')
|
201
|
+
parent_pid = data[3].to_i
|
202
|
+
utime = data[13].to_i
|
203
|
+
ktime = data[14].to_i
|
204
|
+
vss = data[22].to_i / 1024
|
205
|
+
rss = data[23].to_i * 4
|
206
|
+
time = utime + ktime
|
207
|
+
|
208
|
+
{
|
209
|
+
:pid => pid,
|
210
|
+
:uid => uid,
|
211
|
+
:command => command,
|
212
|
+
:cmdline => cmdline,
|
213
|
+
:parent_pid => parent_pid,
|
214
|
+
:mem => rss,
|
215
|
+
:cpu => time,
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
# Process list on OS X or other unixes without a /proc.
|
220
|
+
def os_x_processes
|
221
|
+
raw = os_x_raw_ps.split("\n").slice(1, 99999)
|
222
|
+
raw.map do |line|
|
223
|
+
parse_ps(line)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# ps command used to generate list of processes on non-/proc unixes.
|
228
|
+
def os_x_raw_ps
|
229
|
+
`COLUMNS=9999 ps ax -o "pid uid ppid rss cpu command"`
|
230
|
+
end
|
231
|
+
|
232
|
+
# Parse a single line of the ps command and return the values in a hash
|
233
|
+
# suitable for use in the Rush::Process#new.
|
234
|
+
def parse_ps(line)
|
235
|
+
m = line.split(" ", 6)
|
236
|
+
params = {}
|
237
|
+
params[:pid] = m[0]
|
238
|
+
params[:uid] = m[1]
|
239
|
+
params[:parent_pid] = m[2].to_i
|
240
|
+
params[:mem] = m[3].to_i
|
241
|
+
params[:cpu] = m[4].to_i
|
242
|
+
params[:cmdline] = m[5]
|
243
|
+
params[:command] = params[:cmdline].split(" ").first
|
244
|
+
params
|
245
|
+
end
|
246
|
+
|
247
|
+
# Process list on Windows.
|
248
|
+
def windows_processes
|
249
|
+
require 'win32ole'
|
250
|
+
wmi = WIN32OLE.connect("winmgmts://")
|
251
|
+
wmi.ExecQuery("select * from win32_process").map do |proc_info|
|
252
|
+
parse_oleprocinfo(proc_info)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Parse the Windows OLE process info.
|
257
|
+
def parse_oleprocinfo(proc_info)
|
258
|
+
command = proc_info.Name
|
259
|
+
pid = proc_info.ProcessId
|
260
|
+
uid = 0
|
261
|
+
cmdline = proc_info.CommandLine
|
262
|
+
rss = proc_info.MaximumWorkingSetSize
|
263
|
+
time = proc_info.KernelModeTime.to_i + proc_info.UserModeTime.to_i
|
264
|
+
|
265
|
+
{
|
266
|
+
:pid => pid,
|
267
|
+
:uid => uid,
|
268
|
+
:command => command,
|
269
|
+
:cmdline => cmdline,
|
270
|
+
:mem => rss,
|
271
|
+
:cpu => time,
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
# Returns true if the specified pid is running.
|
276
|
+
def process_alive(pid)
|
277
|
+
::Process.kill(0, pid)
|
278
|
+
true
|
279
|
+
rescue Errno::ESRCH
|
280
|
+
false
|
281
|
+
end
|
282
|
+
|
283
|
+
# Terminate a process, by pid.
|
284
|
+
def kill_process(pid, options={})
|
285
|
+
# time to wait before terminating the process, in seconds
|
286
|
+
wait = options[:wait] || 3
|
287
|
+
|
288
|
+
if wait > 0
|
289
|
+
::Process.kill('TERM', pid)
|
290
|
+
|
291
|
+
# keep trying until it's dead (technique borrowed from god)
|
292
|
+
begin
|
293
|
+
Timeout.timeout(wait) do
|
294
|
+
loop do
|
295
|
+
return if !process_alive(pid)
|
296
|
+
sleep 0.5
|
297
|
+
::Process.kill('TERM', pid) rescue nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
rescue Timeout::Error
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
::Process.kill('KILL', pid) rescue nil
|
305
|
+
|
306
|
+
rescue Errno::ESRCH
|
307
|
+
# if it's dead, great - do nothing
|
308
|
+
end
|
309
|
+
|
310
|
+
def bash(command, user=nil, background=false, reset_environment=false)
|
311
|
+
return bash_background(command, user, reset_environment) if background
|
312
|
+
|
313
|
+
require 'session'
|
314
|
+
sh = Session::Bash.new
|
315
|
+
cmd = reset_environment ? "env -i bash" : "bash"
|
316
|
+
cmd = "cd /; sudo -H -u #{user} \"#{cmd}\"" if user and user != ""
|
317
|
+
out, err = sh.execute(cmd, :stdin => command)
|
318
|
+
retval = sh.status
|
319
|
+
sh.close!
|
320
|
+
|
321
|
+
raise Rush::BashFailed.new(err, out) if retval != 0
|
322
|
+
out
|
323
|
+
end
|
324
|
+
|
325
|
+
def bash_background(command, user, reset_environment)
|
326
|
+
pid = fork do
|
327
|
+
inpipe, outpipe = IO.pipe
|
328
|
+
|
329
|
+
outpipe.write command
|
330
|
+
outpipe.close
|
331
|
+
STDIN.reopen(inpipe)
|
332
|
+
|
333
|
+
close_all_descriptors([inpipe.to_i])
|
334
|
+
|
335
|
+
shell = reset_environment ? "env -i bash" : "bash"
|
336
|
+
|
337
|
+
if user and user != ''
|
338
|
+
exec "cd /; sudo -H -u #{user} \"#{shell}\""
|
339
|
+
else
|
340
|
+
exec shell
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
Process::detach pid
|
345
|
+
|
346
|
+
pid
|
347
|
+
end
|
348
|
+
|
349
|
+
def close_all_descriptors(keep_open = [])
|
350
|
+
3.upto(256) do |fd|
|
351
|
+
next if keep_open.include?(fd)
|
352
|
+
IO::new(fd).close rescue nil
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
####################################
|
357
|
+
|
358
|
+
# Raised when the action passed in by RushServer is not known.
|
359
|
+
class UnknownAction < Exception; end
|
360
|
+
|
361
|
+
# RushServer uses this method to transform a hash (:action plus parameters
|
362
|
+
# specific to that action type) into a method call on the connection. The
|
363
|
+
# returned value must be text so that it can be transmitted across the wire
|
364
|
+
# as an HTTP response.
|
365
|
+
def receive(params)
|
366
|
+
case params[:action]
|
367
|
+
when 'write_file' then write_file(params[:full_path], params[:payload])
|
368
|
+
when 'append_to_file' then append_to_file(params[:full_path], params[:payload])
|
369
|
+
when 'file_contents' then file_contents(params[:full_path])
|
370
|
+
when 'destroy' then destroy(params[:full_path])
|
371
|
+
when 'purge' then purge(params[:full_path])
|
372
|
+
when 'create_dir' then create_dir(params[:full_path])
|
373
|
+
when 'rename' then rename(params[:path], params[:name], params[:new_name], params[:force])
|
374
|
+
when 'copy' then copy(params[:src], params[:dst], params[:force])
|
375
|
+
when 'read_archive' then read_archive(params[:full_path])
|
376
|
+
when 'write_archive' then write_archive(params[:payload], params[:dir], params[:force])
|
377
|
+
when 'index' then index(params[:base_path], params[:glob]).join("\n") + "\n"
|
378
|
+
when 'stat' then YAML.dump(stat(params[:full_path]))
|
379
|
+
when 'set_access' then set_access(params[:full_path], Rush::Access.from_hash(params))
|
380
|
+
when 'size' then size(params[:full_path])
|
381
|
+
when 'processes' then YAML.dump(processes)
|
382
|
+
when 'process_alive' then process_alive(params[:pid]) ? '1' : '0'
|
383
|
+
when 'kill_process' then kill_process(params[:pid].to_i, YAML.load(params[:payload]))
|
384
|
+
when 'bash' then bash(params[:payload], params[:user], params[:background] == 'true', params[:reset_environment] == 'true')
|
385
|
+
else
|
386
|
+
raise UnknownAction
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# No-op for duck typing with remote connection.
|
391
|
+
def ensure_tunnel(options={})
|
392
|
+
end
|
393
|
+
|
394
|
+
# Local connections are always alive.
|
395
|
+
def alive?
|
396
|
+
true
|
397
|
+
end
|
398
|
+
end
|