github-fast-envd/github-fast-env/github-fast-envd.rb

252 lines
5.6 KiB
Ruby
Raw Normal View History

2020-05-24 04:30:04 +02:00
#!/usr/bin/env ruby
require "base64"
2020-05-25 18:13:28 +02:00
require "io/console"
2020-05-24 04:30:04 +02:00
require "socket"
#require "/data/github/current/config/environment"
2020-05-24 07:12:20 +02:00
2020-05-24 04:30:04 +02:00
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
2020-05-26 00:15:22 +02:00
$original_stdin = $stdin.dup
$original_stdout = $stdout.dup
$original_stderr = $stderr.dup
2020-05-24 04:30:04 +02:00
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
2020-05-26 00:15:22 +02:00
$original_stderr.puts "serving control socket"
2020-05-24 04:30:04 +02:00
connection_id = 0
2020-05-25 16:50:05 +02:00
def open_pipe(path)
if File.exist?(path) and File.pipe?(path)
File.unlink(path)
2020-05-24 04:30:04 +02:00
end
2020-05-25 16:50:05 +02:00
File.mkfifo(path, mode = 0600)
2020-05-24 04:30:04 +02:00
2020-05-25 16:50:05 +02:00
path
2020-05-24 04:30:04 +02:00
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)
2020-05-25 16:50:05 +02:00
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")
2020-05-26 03:22:16 +02:00
# TODO: support script arguments
2020-05-25 16:50:05 +02:00
control_socket.puts "named-pipes #{pipe_base_path_encoded}"
2020-05-26 02:59:58 +02:00
stdin = File.open(stdin, "r")
stdin.sync = true
2020-05-26 02:59:58 +02:00
stdout = File.open(stdout, "w")
stdout.sync = true
2020-05-26 02:59:58 +02:00
stderr = File.open(stderr, "w")
stderr.sync = true
2020-05-26 00:15:22 +02:00
$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
2020-05-26 00:15:22 +02:00
def set_up_pseudoterminal(control_socket, pseudoterminal_path)
2020-05-26 02:59:58 +02:00
pseudoterminal_io = File.open(pseudoterminal_path, File::RDWR | File::NOCTTY)
2020-05-25 18:13:28 +02:00
2020-05-26 00:15:22 +02:00
$original_stderr.puts " connecting to pseudoterminal #{pseudoterminal_path}"
2020-05-25 18:13:28 +02:00
2020-05-26 00:15:22 +02:00
$stdin.reopen(pseudoterminal_io)
$stdout.reopen(pseudoterminal_io)
$stderr.reopen(pseudoterminal_io)
2020-05-25 18:13:28 +02:00
2020-05-26 00:15:22 +02:00
$original_stderr.puts " connected to pseudoterminal #{pseudoterminal_path}"
2020-05-25 18:13:28 +02:00
control_socket.puts "ready"
end
2020-05-24 04:30:04 +02:00
while true
control_socket = control_server.accept
2020-05-26 00:15:22 +02:00
$original_stderr.puts "- new connection"
2020-05-24 04:30:04 +02:00
begin
command, arguments = read_command(control_socket)
if command != "new"
raise ClientError.new "unexpected command"
end
2020-05-26 00:15:22 +02:00
if arguments.empty?
2020-05-24 04:30:04 +02:00
raise ClientError.new "malformed command"
end
protocol_version = arguments[0]
if protocol_version != "v1"
raise ClientError.new "unsupported protocol version (#{protocol_version})"
2020-05-24 04:30:04 +02:00
end
mode = arguments[1]
2020-05-26 00:15:22 +02:00
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
2020-05-26 00:15:22 +02:00
script_path = Base64.decode64(arguments.last)
2020-05-24 04:30:04 +02:00
connection_id += 1
child_process = fork {
2020-05-26 03:00:13 +02:00
process_id = Process.pid
control_socket.puts "pid #{process_id}"
2020-05-24 07:11:38 +02:00
exit_code = "unknown"
if mode == "named-pipes"
set_up_named_pipes(control_socket, connection_id)
else
2020-05-26 00:15:22 +02:00
set_up_pseudoterminal(control_socket, pseudoterminal_path)
end
2020-05-24 04:30:04 +02:00
2020-05-26 03:00:13 +02:00
$original_stderr.puts " executing script #{script_path} (#{process_id})"
2020-05-24 04:30:04 +02:00
begin
2020-05-24 04:30:04 +02:00
begin
load script_path, true
2020-05-24 07:11:38 +02:00
rescue SystemExit => error
2020-05-26 00:15:22 +02:00
$original_stderr.puts " exit code: #{error.status}"
2020-05-24 07:11:38 +02:00
exit_code = error.status
rescue StandardError => error
2020-05-26 00:15:22 +02:00
$stdin = $original_stdin
$stdout = $original_stdout
$stderr = $original_stderr
2020-05-24 04:30:04 +02:00
raise ClientScriptError.new error
end
rescue ClientScriptError => error
encoded_error_output = Base64.encode64(error.source.full_message).delete("\n")
2020-05-26 00:15:22 +02:00
$original_stderr.puts " error executing script, ignoring request"
2020-05-24 04:30:04 +02:00
begin
control_socket.puts "script_error #{encoded_error_output}"
rescue
end
rescue ClientError => error
2020-05-26 00:15:22 +02:00
$original_stderr.puts " error communicating with client, ignoring request (#{error})"
2020-05-24 04:30:04 +02:00
begin
control_socket.puts "error #{error}"
rescue
end
rescue StandardError => error
2020-05-26 00:15:22 +02:00
$original_stderr.puts " error, ignoring request (#{error})"
2020-05-24 04:30:04 +02:00
begin
control_socket.puts "error internal server error"
rescue
end
end
begin
2020-05-24 07:11:38 +02:00
control_socket.puts "done #{exit_code}"
2020-05-24 04:30:04 +02:00
rescue
end
control_socket.close
2020-05-26 03:00:13 +02:00
$original_stderr.puts " finished handling request (#{process_id})"
2020-05-24 04:30:04 +02:00
Kernel.exit!
}
Process.detach(child_process)
rescue ClientError => error
2020-05-26 00:15:22 +02:00
$original_stderr.puts " error communicating with client, ignoring request (#{error})"
2020-05-24 04:30:04 +02:00
begin
control_socket.puts "error #{error}"
rescue
end
rescue StandardError => error
2020-05-26 00:15:22 +02:00
$original_stderr.puts " error, ignoring request (#{error})"
2020-05-24 04:30:04 +02:00
begin
control_socket.puts "error internal server error"
rescue
end
end
control_socket.close
end