Skip to content

Commit

Permalink
Scaffolding service
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Mar 7, 2024
1 parent 921eba0 commit 3ead40f
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 4 deletions.
25 changes: 21 additions & 4 deletions tljh_repo2docker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
from .servers import ServersHandler
from .images import ImagesHandler
from .logs import LogsHandler
import sys

# Default CPU period
# See: https://docs.docker.com/config/containers/resource_constraints/#limit-a-containers-access-to-memory#configure-the-default-cfs-scheduler
CPU_PERIOD = 100_000


class SpawnerMixin(Configurable):

"""
Mixin for spawners that derive from DockerSpawner, to use local Docker images
built with tljh-repo2docker.
Expand Down Expand Up @@ -197,9 +197,7 @@ def tljh_custom_jupyterhub_config(c):

machine_profiles = limits.get("machine_profiles", [])

c.JupyterHub.tornado_settings.update(
{"machine_profiles": machine_profiles}
)
c.JupyterHub.tornado_settings.update({"machine_profiles": machine_profiles})

# register the handlers to manage the user images
c.JupyterHub.extra_handlers.extend(
Expand All @@ -215,6 +213,25 @@ def tljh_custom_jupyterhub_config(c):
),
]
)
c.JupyterHub.services.extend(
[
{
"name": "tljh_repo2docker",
"url": "http://127.0.0.1:6789",
"command": [
sys.executable,
"-m",
"tljh_repo2docker",
"--debug",
"--TljhRepo2Docker.ip",
"127.0.0.1",
"--TljhRepo2Docker.port",
"6789",
],
"oauth_no_confirm": True,
}
]
)


@hookimpl
Expand Down
3 changes: 3 additions & 0 deletions tljh_repo2docker/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if __name__ == "__main__":
from .app import main
main()
159 changes: 159 additions & 0 deletions tljh_repo2docker/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""Webservice API."""

import logging
import os
import socket
import typing as tp
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit

from jinja2 import Environment, ChoiceLoader, FileSystemLoader, PackageLoader
from tornado import ioloop, web
from tornado.log import access_log, app_log, gen_log
from traitlets import Dict, Int, List, Unicode, default, validate
from traitlets.config.application import Application
from jupyterhub.utils import url_path_join

if os.environ.get("JUPYTERHUB_API_TOKEN"):
from jupyterhub.services.auth import HubOAuthCallbackHandler
else:

class HubOAuthCallbackHandler:
def get(self):
pass


HERE = Path(__file__).parent


class TljhRepo2Docker(Application):

name = Unicode("tljh-repo2docker")

version = "1.0.0"

port = Int(6789, help="Port of the service", config=True)

api_prefix = Unicode(help="JupyterHub service prefix", config=True)

@default("api_prefix")
def _default_api_prefix(self):
return os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")

ip = Unicode(
"localhost",
config=True,
help="The IP address of the service.",
)

@default("ip")
def _default_ip(self):
"""Return localhost if available, 127.0.0.1 otherwise."""
s = socket.socket()
try:
s.bind(("localhost", 0))
except socket.error as e:
self.log.warning(
"Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e
)
return "127.0.0.1"
else:
s.close()
return "localhost"

@validate("ip")
def _validate_ip(self, proposal):
value = proposal["value"]
if value == "*":
value = ""
return value

template_paths = List(
trait=Unicode,
default_value=None,
allow_none=True,
help="Paths to search for jinja templates, before using the default templates.",
config=True,
)

tornado_settings = Dict(
{},
config=True,
help="Extra settings to apply to tornado application, e.g. headers, ssl, etc",
)

@default("log_level")
def _default_log_level(self):
return logging.INFO

def init_settings(self) -> tp.Dict:
"""Initialize settings for the service application."""
static_path = str(HERE / "static")
static_url_prefix = self.api_prefix + "static/"
env_opt = {"autoescape": True}

env = Environment(
loader=PackageLoader("tljh_repo2docker"),
**env_opt,
)

settings = dict(
log=self.log,
template_path=str(HERE / "templates"),
static_path=static_path,
static_url_prefix=static_url_prefix,
jinja2_env=env,
cookie_secret=os.urandom(32),
)
return settings

def init_handlers(self) -> tp.List:
"""Initialize handlers for service application."""
handlers = []
static_path = str(HERE / "static")
handlers.extend(
[
(
url_path_join(self.api_prefix, r"/static/(.*)"),
web.StaticFileHandler,
{"path": static_path},
),
(self.api_prefix + "oauth_callback", HubOAuthCallbackHandler),
]
)

return handlers

def make_app(self) -> web.Application:
"""Create the tornado web application.
Returns:
The tornado web application.
"""

application = web.Application()
application.listen(self.port, self.ip)
return application

def start(self):
"""Start the server."""
settings = self.init_settings()

self.app = web.Application(**settings)
self.app.settings.update(self.tornado_settings)
handlers = self.init_handlers()
self.app.add_handlers(".*$", handlers)

self.app.listen(self.port, self.ip)
self.ioloop = ioloop.IOLoop.current()
try:
self.log.info(
f"tljh-repo2docker service listening on {self.ip}:{self.port}"
)
self.log.info("Press Ctrl+C to stop")
self.ioloop.start()
except KeyboardInterrupt:
self.log.info("Stopping...")


main = TljhRepo2Docker.launch_instance

0 comments on commit 3ead40f

Please sign in to comment.