commit
86b2f92511
2 changed files with 365 additions and 0 deletions
@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env ruby |
||||
|
||||
require "base64" |
||||
require "optparse" |
||||
require "socket" |
||||
|
||||
usage_output = "USAGE:\n github-fast-env SCRIPT_PATH [options]" |
||||
|
||||
$options = {:verbose => false} |
||||
OptionParser.new do |options| |
||||
options.banner = usage_output |
||||
options.on("-v", "--verbose", "Show verbose output") do |
||||
$options[:verbose] = true |
||||
end |
||||
end.parse! |
||||
|
||||
if not ARGV or ARGV.empty? |
||||
$stderr.puts "error: missing argument (script path)\n\n" |
||||
$stderr.puts usage_output |
||||
exit 1 |
||||
end |
||||
|
||||
if ARGV.length > 1 |
||||
$stderr.puts "error: only one script expected\n\n" |
||||
$stderr.puts usage_output |
||||
exit 1 |
||||
end |
||||
|
||||
script_path = File.realpath(ARGV[0]) |
||||
|
||||
control_socket_path = "/tmp/github-fast-envd.sock" |
||||
|
||||
$control_socket = UNIXSocket.new(control_socket_path) |
||||
|
||||
def log(level, message) |
||||
if level == "error" or $options[:verbose] |
||||
$stderr.puts "[github-fast-env] #{level}: #{message}" |
||||
end |
||||
end |
||||
|
||||
def read_command(control_socket) |
||||
ready_ios = IO.select([control_socket], [], [], 10) |
||||
|
||||
if not ready_ios |
||||
log "error", "timeout while communicating with github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
response = control_socket.readline.strip.split(" ", 2) |
||||
|
||||
if response.empty? |
||||
log "error", "malformed response from github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
command = response[0] |
||||
|
||||
if command == "error" |
||||
if response.length < 2 |
||||
log "error", "malformed error response from github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
error_message = response[1] |
||||
|
||||
log "error", "#{error_message}" |
||||
exit 1 |
||||
end |
||||
|
||||
if command == "script_error" |
||||
if response.length < 2 |
||||
log "error", "malformed script error response from github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
error_message = Base64.decode64(response[1]) |
||||
|
||||
$stderr.puts error_message |
||||
exit 1 |
||||
end |
||||
|
||||
arguments = response[1].nil? ? [] : response[1].split(" ") |
||||
|
||||
return command, arguments |
||||
end |
||||
|
||||
log "info", "connected to control socket" |
||||
|
||||
encoded_script_path = Base64.encode64(script_path).delete("\n") |
||||
$control_socket.puts "new v1 #{encoded_script_path}" |
||||
|
||||
pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil} |
||||
|
||||
while true |
||||
command, arguments = read_command($control_socket) |
||||
|
||||
if command == "ready" |
||||
break |
||||
end |
||||
|
||||
if not ["stdin", "stdout", "stderr"].include?(command) or arguments.empty? |
||||
log "error", "malformed response" |
||||
exit 1 |
||||
end |
||||
|
||||
pipe_path = arguments[0] |
||||
|
||||
log "info", "connecting to #{pipe_path}" |
||||
|
||||
if command == "stdin" |
||||
pipes[command] = File::open(pipe_path, "w") |
||||
else |
||||
pipes[command] = File::open(pipe_path, "r") |
||||
end |
||||
|
||||
pipes[command].sync = true |
||||
|
||||
log "info", "connected" |
||||
end |
||||
|
||||
log "info", "ready" |
||||
$control_socket.puts "ready" |
||||
|
||||
read_ios = [$control_socket, $stdin, pipes["stdout"], pipes["stderr"]] |
||||
|
||||
while read_ios.include?($control_socket) or read_ios.include?(pipes["stdout"]) or read_ios.include?(pipes["stderr"]) |
||||
log "trace", read_ios.inspect |
||||
|
||||
ready_read_ios, _, _ = IO.select(read_ios, [], []) |
||||
|
||||
log "trace", "ready read IOs: #{ready_read_ios.inspect}" |
||||
|
||||
ready_read_ios.each do |ready_read_io| |
||||
begin |
||||
if ready_read_io.equal? $stdin |
||||
log "trace", "writing to stdin" |
||||
pipes["stdin"].write ready_read_io.readpartial(4096) |
||||
elsif ready_read_io.equal? pipes["stdout"] |
||||
log "trace", "reading from stdout" |
||||
$stdout.write ready_read_io.readpartial(4096) |
||||
elsif ready_read_io.equal? pipes["stderr"] |
||||
log "trace", "reading from stderr" |
||||
$stderr.write ready_read_io.readpartial(4096) |
||||
elsif ready_read_io.equal? $control_socket |
||||
log "trace", "reading from control socket" |
||||
command, arguments = read_command($control_socket) |
||||
|
||||
if command != "done" |
||||
log "error", "malformed response from github-fast-envd" |
||||
end |
||||
else |
||||
log "warn", "received input from unknown stream" |
||||
end |
||||
rescue EOFError |
||||
log "trace", "closing stream" |
||||
ready_read_io.close |
||||
end |
||||
end |
||||
|
||||
read_ios = read_ios.select {|x| not x.closed?} |
||||
end |
@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env ruby |
||||
|
||||
require "base64" |
||||
require "socket" |
||||
|
||||
class ClientError < StandardError |
||||
def initialize(message) |
||||
super(message) |
||||
end |
||||
end |
||||
|
||||
class ClientScriptError < StandardError |
||||
def initialize(source) |
||||
super(source.message) |
||||
@source = source |
||||
end |
||||
|
||||
def source |
||||
@source |
||||
end |
||||
end |
||||
|
||||
control_socket_path = "/tmp/github-fast-envd.sock" |
||||
|
||||
if File.exist?(control_socket_path) and File.socket?(control_socket_path) |
||||
File.unlink(control_socket_path) |
||||
end |
||||
|
||||
control_server = UNIXServer.new(control_socket_path) |
||||
File.chmod 0700, control_socket_path |
||||
|
||||
#at_exit do |
||||
# control_server.close |
||||
#end |
||||
|
||||
$stderr.puts "serving control socket" |
||||
|
||||
connection_id = 0 |
||||
|
||||
def open_pipe(connection_id, name) |
||||
pipe_path = "/tmp/github-fast-envd.#{connection_id}.#{name}" |
||||
|
||||
if File.exist?(pipe_path) and File.pipe?(pipe_path) |
||||
File.unlink(pipe_path) |
||||
end |
||||
|
||||
File.mkfifo(pipe_path, mode = 0600) |
||||
|
||||
pipe_path |
||||
end |
||||
|
||||
def read_command(control_socket) |
||||
ready_ios = IO.select([control_socket], [], [], 10) |
||||
|
||||
if not ready_ios |
||||
log "error", "timeout while communicating with github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
response = control_socket.readline.strip.split(" ", 2) |
||||
|
||||
if response.empty? |
||||
log "error", "malformed response from github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
command = response[0] |
||||
|
||||
if command == "error" |
||||
if response.length < 2 |
||||
log "error", "malformed error response from github-fast-envd" |
||||
exit 1 |
||||
end |
||||
|
||||
error_message = response[1] |
||||
|
||||
log "error", "#{error_message}" |
||||
exit 1 |
||||
end |
||||
|
||||
arguments = response[1].nil? ? [] : response[1].split(" ") |
||||
|
||||
return command, arguments |
||||
end |
||||
|
||||
while true |
||||
control_socket = control_server.accept |
||||
$stderr.puts "- new connection" |
||||
|
||||
begin |
||||
command, arguments = read_command(control_socket) |
||||
|
||||
if command != "new" |
||||
raise ClientError.new "unexpected command" |
||||
end |
||||
|
||||
if arguments.length != 2 |
||||
raise ClientError.new "malformed command" |
||||
end |
||||
|
||||
if arguments[0] != "v1" |
||||
raise ClientError.new "unsupported protocol version" |
||||
end |
||||
|
||||
connection_id += 1 |
||||
|
||||
child_process = fork { |
||||
original_stdin = $stdin.dup |
||||
original_stdout = $stdout.dup |
||||
original_stderr = $stderr.dup |
||||
|
||||
begin |
||||
stdin = open_pipe(connection_id, "stdin") |
||||
control_socket.puts "stdin #{stdin}" |
||||
stdin = File::open(stdin, "r") |
||||
stdin.sync = true |
||||
|
||||
stdout = open_pipe(connection_id, "stdout") |
||||
control_socket.puts "stdout #{stdout}" |
||||
stdout = File::open(stdout, "w") |
||||
stdout.sync = true |
||||
|
||||
stderr = open_pipe(connection_id, "stderr") |
||||
control_socket.puts "stderr #{stderr}" |
||||
stderr = File::open(stderr, "w") |
||||
stderr.sync = true |
||||
|
||||
$stderr.puts " set up pipes" |
||||
control_socket.puts "ready" |
||||
|
||||
response = control_socket.readline.strip |
||||
|
||||
if response != "ready" |
||||
raise ClientError.new "invalid command" |
||||
Kernel.exit! |
||||
end |
||||
|
||||
script_path = Base64.decode64(arguments[1]) |
||||
$stderr.puts " executing script " + script_path |
||||
|
||||
$stdin.reopen(stdin) |
||||
$stdout.reopen(stdout) |
||||
$stderr.reopen(stderr) |
||||
|
||||
begin |
||||
load script_path, true |
||||
rescue => error |
||||
$stdin = original_stdin |
||||
$stdout = original_stdout |
||||
$stderr = original_stderr |
||||
|
||||
raise ClientScriptError.new error |
||||
end |
||||
rescue ClientScriptError => error |
||||
# TODO: Restore pipes to make sure that syntax errors are caught |
||||
encoded_error_output = Base64.encode64(error.source.full_message).delete("\n") |
||||
original_stderr.puts " error executing script, ignoring request" |
||||
# TODO: if the begin/rescue blog has syntax errors, these go unnoticed |
||||
begin |
||||
control_socket.puts "script_error #{encoded_error_output}" |
||||
rescue |
||||
end |
||||
rescue ClientError => error |
||||
original_stderr.puts " error communicating with client, ignoring request (#{error})" |
||||
begin |
||||
control_socket.puts "error #{error}" |
||||
rescue |
||||
end |
||||
rescue StandardError => error |
||||
original_stderr.puts " error, ignoring request (#{error})" |
||||
begin |
||||
control_socket.puts "error internal server error" |
||||
rescue |
||||
end |
||||
end |
||||
|
||||
begin |
||||
control_socket.puts "done" |
||||
rescue |
||||
end |
||||
control_socket.close |
||||
|
||||
original_stderr.puts " finished handling request" |
||||
|
||||
Kernel.exit! |
||||
} |
||||
|
||||
Process.detach(child_process) |
||||
rescue ClientError => error |
||||
$stderr.puts " error communicating with client, ignoring request (#{error})" |
||||
begin |
||||
control_socket.puts "error #{error}" |
||||
rescue |
||||
end |
||||
rescue StandardError => error |
||||
$stderr.puts " error, ignoring request (#{error})" |
||||
begin |
||||
control_socket.puts "error internal server error" |
||||
rescue |
||||
end |
||||
end |
||||
|
||||
control_socket.close |
||||
end |
Loading…
Reference in new issue