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

264 lines
5.9 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 "optparse"
require "socket"
usage_output = "USAGE:\n github-fast-env SCRIPT_PATH [options]"
2020-05-25 18:13:28 +02:00
$options = {:verbose => false, :interactive => false}
2020-05-24 04:30:04 +02:00
OptionParser.new do |options|
options.banner = usage_output
options.on("-v", "--verbose", "Show verbose output") do
$options[:verbose] = true
end
2020-05-25 18:13:28 +02:00
options.on("-i", "--interactive", "Run an interactive session using a pseudoterminal") do
$options[:interactive] = true
end
2020-05-24 04:30:04 +02:00
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
2020-05-24 07:11:20 +02:00
# TODO: support arguments
2020-05-24 04:30:04 +02:00
script_path = File.realpath(ARGV[0])
control_socket_path = "/tmp/github-fast-envd.sock"
2020-05-25 18:13:28 +02:00
$original_stdin = $stdin.dup
$original_stdout = $stdout.dup
2020-05-26 02:59:22 +02:00
$original_stderr = $stderr.dup
2020-05-25 18:13:28 +02:00
2020-05-24 04:30:04 +02:00
def log(level, message)
if level == "error" or $options[:verbose]
2020-05-26 02:59:22 +02:00
$original_stderr.puts "[github-fast-env] #{level}: #{message}"
2020-05-24 04:30:04 +02:00
end
end
2020-05-26 03:07:50 +02:00
begin
$control_socket = UNIXSocket.new(control_socket_path)
rescue StandardError => error
log "error", "could not connect to github-fast-envd socket"
exit 1
end
2020-05-26 03:00:13 +02:00
$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
2020-05-26 00:15:22 +02:00
def read_command
ready_ios = IO.select([$control_socket], [], [], 10)
2020-05-24 04:30:04 +02:00
if not ready_ios
log "error", "timeout while communicating with github-fast-envd"
exit 1
end
2020-05-26 00:15:22 +02:00
response = $control_socket.readline.strip.split(" ", 2)
2020-05-24 04:30:04 +02:00
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")
2020-05-25 18:13:28 +02:00
2020-05-26 00:15:22 +02:00
read_ios = [$control_socket]
2020-05-25 18:13:28 +02:00
if $options[:interactive]
2020-05-26 00:15:22 +02:00
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}"
2020-05-25 18:13:28 +02:00
else
$control_socket.puts "new v1 named-pipes #{encoded_script_path}"
end
2020-05-24 04:30:04 +02:00
pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil}
while true
2020-05-26 00:15:22 +02:00
command, arguments = read_command
2020-05-24 04:30:04 +02:00
2020-05-26 03:00:13 +02:00
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"
2020-05-24 04:30:04 +02:00
break
2020-05-25 16:50:05 +02:00
elsif command == "named-pipes"
if arguments.empty?
log "error", "malformed response"
exit 1
end
2020-05-24 04:30:04 +02:00
2020-05-25 16:50:05 +02:00
pipe_base_path = Base64.decode64(arguments[0])
2020-05-24 04:30:04 +02:00
2020-05-25 16:50:05 +02:00
log "info", "connecting to named pipes at #{pipe_base_path}"
2020-05-24 04:30:04 +02:00
2020-05-26 02:59:58 +02:00
pipes["stdin"] = File.open("#{pipe_base_path}.stdin", "w")
2020-05-25 16:50:05 +02:00
pipes["stdin"].sync = true
2020-05-26 02:59:58 +02:00
pipes["stdout"] = File.open("#{pipe_base_path}.stdout", "r")
2020-05-25 16:50:05 +02:00
pipes["stdout"].sync = true
2020-05-26 02:59:58 +02:00
pipes["stderr"] = File.open("#{pipe_base_path}.stderr", "r")
2020-05-25 16:50:05 +02:00
pipes["stderr"].sync = true
2020-05-24 04:30:04 +02:00
2020-05-26 00:15:22 +02:00
read_ios += [$stdin, pipes["stdout"], pipes["stderr"]]
2020-05-25 18:13:28 +02:00
2020-05-25 16:50:05 +02:00
log "info", "connected via named pipes"
2020-05-25 18:13:28 +02:00
2020-05-26 00:15:22 +02:00
log "info", "ready"
$control_socket.puts "ready"
2020-05-24 04:30:04 +02:00
else
2020-05-25 16:50:05 +02:00
log "error", "malformed response"
exit 1
2020-05-24 04:30:04 +02:00
end
end
2020-05-24 07:11:38 +02:00
exit_code = "unknown"
2020-05-24 04:30:04 +02:00
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"
2020-05-26 00:15:22 +02:00
command, arguments = read_command
2020-05-24 04:30:04 +02:00
if command != "done"
log "error", "malformed response from github-fast-envd"
end
2020-05-24 07:11:38 +02:00
if !arguments.empty?
exit_code = arguments[0]
end
2020-05-24 04:30:04 +02:00
else
log "warn", "received input from unknown stream"
end
rescue EOFError
2020-05-25 18:13:28 +02:00
log "trace", "closing stream #{ready_read_io}"
2020-05-24 04:30:04 +02:00
ready_read_io.close
end
end
read_ios = read_ios.select {|x| not x.closed?}
end
2020-05-24 07:11:38 +02:00
exit_code_is_numeric = Integer(exit_code) != nil rescue false
if exit_code_is_numeric
exit Integer(exit_code)
end