generated from ebmdatalab/notebook-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run.py
120 lines (99 loc) · 3.27 KB
/
run.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""A cross-platform script to build and start a notebook, open a web
browser on the correct port, and handle shutdowns gracefully
"""
import os
import signal
import subprocess
import socket
import sys
import time
import urllib.request
import webbrowser
tag = "datalab-notebook"
current_dir = os.getcwd()
target_dir = "/home/app/notebook"
def await_jupyter_http(port):
"""Wait up to 10 seconds for Jupyter to be available
"""
print(f"Waiting for Jupyter to be ready on port {port}")
timeout = 10
increment = 0.1
counter = 0
url = f"http://localhost:{port}"
while counter < (timeout / increment):
try:
with urllib.request.urlopen(url, timeout=timeout):
return
except ConnectionResetError:
counter += 1
time.sleep(increment)
except socket.timeout:
break
raise SystemError(f"Unable to reach Jupyter at {url}")
def stream_subprocess_output(cmd):
"""Stream stdout and stderr of `cmd` in a subprocess to stdout
"""
with subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
universal_newlines=True,
) as p:
for line in p.stdout:
print(line, end="")
p.wait()
if p.returncode > 0:
raise subprocess.CalledProcessError(cmd=cmd, returncode=p.returncode)
def docker_build(tag):
"""Build container for Dockerfile in current directory
"""
print(
"Building docker image. This may take some time (particularly on the first run)..."
)
buildcmd = ["docker", "build", "-t", tag, "-f", "Dockerfile", "."]
stream_subprocess_output(buildcmd)
def docker_run(tag):
"""Run docker in background, and install signal handler to stop it
again
"""
print("Running docker...")
runcmd = [
"docker",
"run",
"--detach", # in the background, so we can find out the port it's bound to
"--rm", # clean up the container after it's stopped
"--mount",
f"source={current_dir},dst={target_dir},type=bind",
"--publish-all",
tag,
]
completed_process = subprocess.run(runcmd, check=True, capture_output=True)
container_id = completed_process.stdout.decode("utf8").strip()
def stop_handler(sig, frame):
print("Stopping docker...")
subprocess.run(["docker", "kill", container_id], check=True)
sys.exit(0)
signal.signal(signal.SIGINT, stop_handler)
return container_id
def docker_port(container_id):
"""Return the port that the specified container is listening on
"""
completed_process = subprocess.run(
["docker", "port", container_id], check=True, capture_output=True
)
port_mapping = completed_process.stdout.decode("utf8").strip()
port = port_mapping.split(":")[-1]
return port
def main():
docker_build(tag)
container_id = docker_run(tag)
port = docker_port(container_id)
await_jupyter_http(port)
webbrowser.open(f"http://localhost:{port}", new=2) # Open in a new tab
print(
"To stop this docker container, use Ctrl+ C, or the File -> Shut Down menu in Jupyter Lab"
)
stream_subprocess_output(["docker", "logs", "--follow", container_id])
if __name__ == "__main__":
main()