Move files to better subdirectory
This commit is contained in:
263
github-fast-env/github-fast-env.rb
Normal file
263
github-fast-env/github-fast-env.rb
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "base64"
|
||||
require "io/console"
|
||||
require "optparse"
|
||||
require "socket"
|
||||
|
||||
usage_output = "USAGE:\n github-fast-env SCRIPT_PATH [options]"
|
||||
|
||||
$options = {:verbose => false, :interactive => false}
|
||||
OptionParser.new do |options|
|
||||
options.banner = usage_output
|
||||
options.on("-v", "--verbose", "Show verbose output") do
|
||||
$options[:verbose] = true
|
||||
end
|
||||
options.on("-i", "--interactive", "Run an interactive session using a pseudoterminal") do
|
||||
$options[:interactive] = 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
|
||||
|
||||
# TODO: support arguments
|
||||
script_path = File.realpath(ARGV[0])
|
||||
|
||||
control_socket_path = "/tmp/github-fast-envd.sock"
|
||||
|
||||
$original_stdin = $stdin.dup
|
||||
$original_stdout = $stdout.dup
|
||||
$original_stderr = $stderr.dup
|
||||
|
||||
def log(level, message)
|
||||
if level == "error" or $options[:verbose]
|
||||
$original_stderr.puts "[github-fast-env] #{level}: #{message}"
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
$control_socket = UNIXSocket.new(control_socket_path)
|
||||
rescue StandardError => error
|
||||
log "error", "could not connect to github-fast-envd socket"
|
||||
exit 1
|
||||
end
|
||||
|
||||
$remote_process_id = nil
|
||||
|
||||
Signal.trap("HUP") do
|
||||
if $remote_process_id
|
||||
Process.kill("HUP", $remote_process_id)
|
||||
else
|
||||
Signal.trap("HUP", "DEFAULT")
|
||||
Process.kill("HUP", 0)
|
||||
end
|
||||
end
|
||||
|
||||
Signal.trap("INT") do
|
||||
if $remote_process_id
|
||||
Process.kill("INT", $remote_process_id)
|
||||
else
|
||||
Signal.trap("INT", "DEFAULT")
|
||||
Process.kill("INT", 0)
|
||||
end
|
||||
end
|
||||
|
||||
Signal.trap("QUIT") do
|
||||
if $remote_process_id
|
||||
Process.kill("QUIT", $remote_process_id)
|
||||
else
|
||||
Signal.trap("QUIT", "DEFAULT")
|
||||
Process.kill("QUIT", 0)
|
||||
end
|
||||
end
|
||||
|
||||
Signal.trap("TERM") do
|
||||
if $remote_process_id
|
||||
Process.kill("TERM", $remote_process_id)
|
||||
else
|
||||
Signal.trap("TERM", "DEFAULT")
|
||||
Process.kill("TERM", 0)
|
||||
end
|
||||
end
|
||||
|
||||
Signal.trap("TSTP") do
|
||||
if $remote_process_id
|
||||
Process.kill("TSTP", $remote_process_id)
|
||||
end
|
||||
|
||||
Signal.trap("TSTP", "DEFAULT")
|
||||
Process.kill("TSTP", 0)
|
||||
end
|
||||
|
||||
Signal.trap("CONT") do
|
||||
if $remote_process_id
|
||||
Process.kill("CONT", $remote_process_id)
|
||||
end
|
||||
|
||||
Signal.trap("CONT", "DEFAULT")
|
||||
Process.kill("CONT", 0)
|
||||
end
|
||||
|
||||
def read_command
|
||||
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")
|
||||
|
||||
read_ios = [$control_socket]
|
||||
|
||||
if $options[:interactive]
|
||||
pseudoterminal_path = File.readlink("/proc/self/fd/0")
|
||||
encoded_pseudoterminal_path = Base64.encode64(pseudoterminal_path).delete("\n")
|
||||
|
||||
$control_socket.puts "new v1 pseudoterminal #{encoded_pseudoterminal_path} #{encoded_script_path}"
|
||||
else
|
||||
$control_socket.puts "new v1 named-pipes #{encoded_script_path}"
|
||||
end
|
||||
|
||||
pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil}
|
||||
|
||||
while true
|
||||
command, arguments = read_command
|
||||
|
||||
if command == "pid"
|
||||
if arguments.empty?
|
||||
log "error", "malformed response"
|
||||
exit 1
|
||||
end
|
||||
|
||||
$remote_process_id = arguments[0].to_i
|
||||
log "trace", "remote process ID: #{$remote_process_id}"
|
||||
elsif command == "ready"
|
||||
break
|
||||
elsif command == "named-pipes"
|
||||
if arguments.empty?
|
||||
log "error", "malformed response"
|
||||
exit 1
|
||||
end
|
||||
|
||||
pipe_base_path = Base64.decode64(arguments[0])
|
||||
|
||||
log "info", "connecting to named pipes at #{pipe_base_path}"
|
||||
|
||||
pipes["stdin"] = File.open("#{pipe_base_path}.stdin", "w")
|
||||
pipes["stdin"].sync = true
|
||||
pipes["stdout"] = File.open("#{pipe_base_path}.stdout", "r")
|
||||
pipes["stdout"].sync = true
|
||||
pipes["stderr"] = File.open("#{pipe_base_path}.stderr", "r")
|
||||
pipes["stderr"].sync = true
|
||||
|
||||
read_ios += [$stdin, pipes["stdout"], pipes["stderr"]]
|
||||
|
||||
log "info", "connected via named pipes"
|
||||
|
||||
log "info", "ready"
|
||||
$control_socket.puts "ready"
|
||||
else
|
||||
log "error", "malformed response"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
exit_code = "unknown"
|
||||
|
||||
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
|
||||
|
||||
if command != "done"
|
||||
log "error", "malformed response from github-fast-envd"
|
||||
end
|
||||
|
||||
if !arguments.empty?
|
||||
exit_code = arguments[0]
|
||||
end
|
||||
else
|
||||
log "warn", "received input from unknown stream"
|
||||
end
|
||||
rescue EOFError
|
||||
log "trace", "closing stream #{ready_read_io}"
|
||||
ready_read_io.close
|
||||
end
|
||||
end
|
||||
|
||||
read_ios = read_ios.select {|x| not x.closed?}
|
||||
end
|
||||
|
||||
exit_code_is_numeric = Integer(exit_code) != nil rescue false
|
||||
|
||||
if exit_code_is_numeric
|
||||
exit Integer(exit_code)
|
||||
end
|
251
github-fast-env/github-fast-envd.rb
Normal file
251
github-fast-env/github-fast-envd.rb
Normal file
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "base64"
|
||||
require "io/console"
|
||||
require "socket"
|
||||
|
||||
#require "/data/github/current/config/environment"
|
||||
|
||||
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
|
||||
|
||||
$original_stdin = $stdin.dup
|
||||
$original_stdout = $stdout.dup
|
||||
$original_stderr = $stderr.dup
|
||||
|
||||
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
|
||||
|
||||
$original_stderr.puts "serving control socket"
|
||||
|
||||
connection_id = 0
|
||||
|
||||
def open_pipe(path)
|
||||
if File.exist?(path) and File.pipe?(path)
|
||||
File.unlink(path)
|
||||
end
|
||||
|
||||
File.mkfifo(path, mode = 0600)
|
||||
|
||||
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
|
||||
|
||||
def set_up_named_pipes(control_socket, connection_id)
|
||||
pipe_base_path = "/tmp/github-fast-envd.#{connection_id}"
|
||||
pipe_base_path_encoded = Base64.encode64(pipe_base_path).delete("\n")
|
||||
|
||||
stdin = open_pipe("#{pipe_base_path}.stdin")
|
||||
stdout = open_pipe("#{pipe_base_path}.stdout")
|
||||
stderr = open_pipe("#{pipe_base_path}.stderr")
|
||||
|
||||
# TODO: support script arguments
|
||||
control_socket.puts "named-pipes #{pipe_base_path_encoded}"
|
||||
|
||||
stdin = File.open(stdin, "r")
|
||||
stdin.sync = true
|
||||
stdout = File.open(stdout, "w")
|
||||
stdout.sync = true
|
||||
stderr = File.open(stderr, "w")
|
||||
stderr.sync = true
|
||||
|
||||
$original_stderr.puts " set up named pipes"
|
||||
|
||||
control_socket.puts "ready"
|
||||
|
||||
response = control_socket.readline.strip
|
||||
|
||||
if response != "ready"
|
||||
raise ClientError.new "invalid command"
|
||||
Kernel.exit!
|
||||
end
|
||||
|
||||
$stdin.reopen(stdin)
|
||||
$stdout.reopen(stdout)
|
||||
$stderr.reopen(stderr)
|
||||
end
|
||||
|
||||
def set_up_pseudoterminal(control_socket, pseudoterminal_path)
|
||||
pseudoterminal_io = File.open(pseudoterminal_path, File::RDWR | File::NOCTTY)
|
||||
|
||||
$original_stderr.puts " connecting to pseudoterminal #{pseudoterminal_path}"
|
||||
|
||||
$stdin.reopen(pseudoterminal_io)
|
||||
$stdout.reopen(pseudoterminal_io)
|
||||
$stderr.reopen(pseudoterminal_io)
|
||||
|
||||
$original_stderr.puts " connected to pseudoterminal #{pseudoterminal_path}"
|
||||
|
||||
control_socket.puts "ready"
|
||||
end
|
||||
|
||||
while true
|
||||
control_socket = control_server.accept
|
||||
$original_stderr.puts "- new connection"
|
||||
|
||||
begin
|
||||
command, arguments = read_command(control_socket)
|
||||
|
||||
if command != "new"
|
||||
raise ClientError.new "unexpected command"
|
||||
end
|
||||
|
||||
if arguments.empty?
|
||||
raise ClientError.new "malformed command"
|
||||
end
|
||||
|
||||
protocol_version = arguments[0]
|
||||
|
||||
if protocol_version != "v1"
|
||||
raise ClientError.new "unsupported protocol version (#{protocol_version})"
|
||||
end
|
||||
|
||||
mode = arguments[1]
|
||||
|
||||
if mode == "named-pipes"
|
||||
if arguments.length < 3
|
||||
raise ClientError.new "malformed command"
|
||||
end
|
||||
elsif mode == "pseudoterminal"
|
||||
if arguments.length < 4
|
||||
raise ClientError.new "malformed command"
|
||||
end
|
||||
|
||||
pseudoterminal_path = Base64.decode64(arguments[2])
|
||||
else
|
||||
raise ClientError.new "unknown mode (#{mode})"
|
||||
end
|
||||
|
||||
script_path = Base64.decode64(arguments.last)
|
||||
|
||||
connection_id += 1
|
||||
|
||||
child_process = fork {
|
||||
process_id = Process.pid
|
||||
control_socket.puts "pid #{process_id}"
|
||||
|
||||
exit_code = "unknown"
|
||||
|
||||
if mode == "named-pipes"
|
||||
set_up_named_pipes(control_socket, connection_id)
|
||||
else
|
||||
set_up_pseudoterminal(control_socket, pseudoterminal_path)
|
||||
end
|
||||
|
||||
$original_stderr.puts " executing script #{script_path} (#{process_id})"
|
||||
|
||||
begin
|
||||
begin
|
||||
load script_path, true
|
||||
rescue SystemExit => error
|
||||
$original_stderr.puts " exit code: #{error.status}"
|
||||
exit_code = error.status
|
||||
rescue StandardError => error
|
||||
$stdin = $original_stdin
|
||||
$stdout = $original_stdout
|
||||
$stderr = $original_stderr
|
||||
|
||||
raise ClientScriptError.new error
|
||||
end
|
||||
rescue ClientScriptError => error
|
||||
encoded_error_output = Base64.encode64(error.source.full_message).delete("\n")
|
||||
$original_stderr.puts " error executing script, ignoring request"
|
||||
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 #{exit_code}"
|
||||
rescue
|
||||
end
|
||||
control_socket.close
|
||||
|
||||
$original_stderr.puts " finished handling request (#{process_id})"
|
||||
|
||||
Kernel.exit!
|
||||
}
|
||||
|
||||
Process.detach(child_process)
|
||||
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
|
||||
|
||||
control_socket.close
|
||||
end
|
Reference in New Issue
Block a user