repo2docker/repo2docker/buildpacks/repo2docker-entrypoint

115 wiersze
3.3 KiB
Plaintext
Executable File

#!/usr/local/bin/python3-login
# note: must run on Python >= 3.5, which mainly means no f-strings
# goals:
# - load environment variables from a login shell (bash -l)
# - preserve signal handling of subprocess (kill -TERM and friends)
# - tee output to a log file
import fcntl
import os
import select
import signal
import subprocess
import sys
import tempfile
# output chunk size to read
CHUNK_SIZE = 1024
# signals to be forwarded to the child
# everything catchable, excluding SIGCHLD
SIGNALS = set(signal.Signals) - {signal.SIGKILL, signal.SIGSTOP, signal.SIGCHLD}
def main():
# open log file to send output to;
# preferred location of log file is:
# 1. REPO_DIR env variable
# 2. current working directory: "."
# 3. default temp directory for the OS (e.g. /tmp for linux)
log_dirs = [".", tempfile.gettempdir()]
log_file = None
if "REPO_DIR" in os.environ:
log_dirs.insert(0, os.environ["REPO_DIR"])
for d in log_dirs:
log_path = os.path.join(d, ".jupyter-server-log.txt")
try:
log_file = open(log_path, "ab")
except Exception:
continue
else:
# success
break
# raise Exception if log_file could not be set
if log_file is None:
raise Exception("Could not open '.jupyter-server-log.txt' log file " )
# build the command
# like `exec "$@"`
command = sys.argv[1:]
# load entrypoint override from env
r2d_entrypoint = os.environ.get("R2D_ENTRYPOINT")
if r2d_entrypoint:
command.insert(0, r2d_entrypoint)
# launch the subprocess
child = subprocess.Popen(
command,
bufsize=1,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
# hook up ~all signals so that every signal the parent gets,
# the children also get
def relay_signal(sig, frame):
"""Relay a signal to children"""
# DEBUG: show signal
child.send_signal(sig)
for signum in SIGNALS:
signal.signal(signum, relay_signal)
# tee output from child to both our stdout and the log file
def tee(chunk):
"""Tee output from child to both our stdout and the log file"""
for f in [sys.stdout.buffer, log_file]:
f.write(chunk)
f.flush()
# make stdout pipe non-blocking
# this means child.stdout.read(nbytes)
# will always return immediately, even if there's nothing to read
flags = fcntl.fcntl(child.stdout, fcntl.F_GETFL)
fcntl.fcntl(child.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
poller = select.poll()
poller.register(child.stdout)
# while child is running, constantly relay output
while child.poll() is None:
chunk = child.stdout.read(CHUNK_SIZE)
if chunk:
tee(chunk)
else:
# empty chunk means nothing to read
# wait for output on the pipe
# timeout is in milliseconds
poller.poll(1000)
# child has exited, continue relaying any remaining output
# At this point, read() will return an empty string when it's done
chunk = child.stdout.read()
while chunk:
tee(chunk)
chunk = child.stdout.read()
# make our returncode match the child's returncode
sys.exit(child.returncode)
if __name__ == "__main__":
main()