Skip to content

Commit

Permalink
[wip] Add service handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Mar 9, 2024
1 parent 5516a6e commit 02aa466
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 20 deletions.
4 changes: 2 additions & 2 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
git+https://github.com/jupyterhub/the-littlest-jupyterhub
jupyterhub~=1.5
notebook<7
# jupyterhub~=1.5
# notebook<7
pytest
pytest-aiohttp
pytest-asyncio
Expand Down
1 change: 1 addition & 0 deletions tljh_repo2docker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def tljh_custom_jupyterhub_config(c):
"6789",
],
"oauth_no_confirm": True,
'admin': True,
}
]
)
Expand Down
33 changes: 26 additions & 7 deletions tljh_repo2docker/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from traitlets import Dict, Int, List, Unicode, default, validate
from traitlets.config.application import Application
from jupyterhub.utils import url_path_join
from jupyterhub.app import DATA_FILES_PATH
from jupyterhub.handlers.static import LogoHandler
from .builder import BuildHandler
from .docker import list_images
from .handlers.servers import ServersHandler
Expand All @@ -32,7 +34,6 @@ def get(self):


class TljhRepo2Docker(Application):

name = Unicode("tljh-repo2docker")

version = "1.0.0"
Expand Down Expand Up @@ -87,6 +88,16 @@ def _validate_ip(self, proposal):
config=True,
)

logo_file = Unicode(
"",
help="Specify path to a logo image to override the Jupyter logo in the banner.",
config=True,
)

@default("logo_file")
def _logo_file_default(self):
return str(HERE / "static/images/jupyterhub-80.png")

tornado_settings = Dict(
{},
config=True,
Expand All @@ -99,43 +110,51 @@ def _default_log_level(self):

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

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

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

def init_handlers(self) -> tp.List:
"""Initialize handlers for service application."""
handlers = []
static_path = str(HERE / "static")
server_url = url_path_join(self.service_prefix, r"servers")
handlers.extend(
[
(
url_path_join(self.service_prefix, r"/static/(.*)"),
url_path_join(self.service_prefix, "logo"),
LogoHandler,
{"path": self.logo_file},
),
(
url_path_join(self.service_prefix, r"/service_static/(.*)"),
web.StaticFileHandler,
{"path": static_path},
),
(
url_path_join(self.service_prefix, "oauth_callback"),
HubOAuthCallbackHandler,
),
(url_path_join(self.service_prefix, r"servers"), ServersHandler),
(self.service_prefix, web.RedirectHandler, {"url": server_url}),
(server_url, ServersHandler),
# (url_path_join(self.service_prefix, r"environments"), ImagesHandler),
# (url_path_join(self.service_prefix, r"api/environments"), BuildHandler),
# (
Expand Down
19 changes: 14 additions & 5 deletions tljh_repo2docker/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
from jupyterhub.utils import url_path_join
from jupyterhub.services.auth import HubOAuthenticated
from jinja2 import Template

import requests
JUPYTERHUB_API_URL = os.environ.get("JUPYTERHUB_API_URL", None)

API_TOKEN = os.environ.get("JUPYTERHUB_API_TOKEN", None)

class BaseHandler(HubOAuthenticated, web.RequestHandler):
"""
Base handler for tljh_repo2docker service
"""

def fetch_user(self) -> Dict:
async def fetch_user(self) -> Dict:
user = self.current_user
print("AAAAAAAAAAAAA", user)
return user
url = url_path_join(JUPYTERHUB_API_URL, 'users', user['name'])
user_model = requests.get(url + '?include_stopped_servers', headers={"Authorization": "token " + API_TOKEN})
print("AAAAAAAAAAAAA", user_model.json())

return user_model

def get_template(self, name: str) -> Template:
"""Return the jinja template object for a given name
Expand All @@ -39,9 +42,15 @@ def render_template(self, name: str, **kwargs) -> str:
Returns:
The generated template
"""
user = self.current_user
template_ns = dict(
service_prefix=self.settings.get("service_prefix", "/"),
hub_prefix=self.settings.get("hub_prefix", "/"),
base_url=self.settings.get("base_url", "/"),
static_url=self.static_url,
xsrf_token=self.xsrf_token.decode('ascii'),
user=user,
admin_access=user['admin']
)
template_ns.update(kwargs)
template = self.get_template(name)
Expand Down
146 changes: 146 additions & 0 deletions tljh_repo2docker/handlers/docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import json

from urllib.parse import urlparse

from aiodocker import Docker


async def list_images():
"""
Retrieve local images built by repo2docker
"""
async with Docker() as docker:
r2d_images = await docker.images.list(
filters=json.dumps({"dangling": ["false"], "label": ["repo2docker.ref"]})
)
images = [
{
"repo": image["Labels"]["repo2docker.repo"],
"ref": image["Labels"]["repo2docker.ref"],
"image_name": image["Labels"]["tljh_repo2docker.image_name"],
"display_name": image["Labels"]["tljh_repo2docker.display_name"],
"mem_limit": image["Labels"]["tljh_repo2docker.mem_limit"],
"cpu_limit": image["Labels"]["tljh_repo2docker.cpu_limit"],
"status": "built",
}
for image in r2d_images
if "tljh_repo2docker.image_name" in image["Labels"]
]
return images


async def list_containers():
"""
Retrieve the list of local images being built by repo2docker.
Images are built in a Docker container.
"""
async with Docker() as docker:
r2d_containers = await docker.containers.list(
filters=json.dumps({"label": ["repo2docker.ref"]})
)
containers = [
{
"repo": container["Labels"]["repo2docker.repo"],
"ref": container["Labels"]["repo2docker.ref"],
"image_name": container["Labels"]["repo2docker.build"],
"display_name": container["Labels"]["tljh_repo2docker.display_name"],
"mem_limit": container["Labels"]["tljh_repo2docker.mem_limit"],
"cpu_limit": container["Labels"]["tljh_repo2docker.cpu_limit"],
"status": "building",
}
for container in r2d_containers
if "repo2docker.build" in container["Labels"]
]
return containers


async def build_image(
repo, ref, name="", memory=None, cpu=None, username=None, password=None,
extra_buildargs=None
):
"""
Build an image given a repo, ref and limits
"""
ref = ref or "HEAD"
if len(ref) >= 40:
ref = ref[:7]

# default to the repo name if no name specified
# and sanitize the name of the docker image
name = name or urlparse(repo).path.strip("/")
name = name.lower().replace("/", "-")
image_name = f"{name}:{ref}"

# memory is specified in GB
memory = f"{memory}G" if memory else ""
cpu = cpu or ""

# add extra labels to set additional image properties
labels = [
f"tljh_repo2docker.display_name={name}",
f"tljh_repo2docker.image_name={image_name}",
f"tljh_repo2docker.mem_limit={memory}",
f"tljh_repo2docker.cpu_limit={cpu}",
]
cmd = [
"jupyter-repo2docker",
"--ref",
ref,
"--user-name",
"jovyan",
"--user-id",
"1100",
"--no-run",
"--image-name",
image_name,
]

for label in labels:
cmd += [
"--label",
label
]

for barg in extra_buildargs or []:
cmd += [
"--build-arg",
barg
]

cmd.append(repo)

config = {
"Cmd": cmd,
"Image": "quay.io/jupyterhub/repo2docker:main",
"Labels": {
"repo2docker.repo": repo,
"repo2docker.ref": ref,
"repo2docker.build": image_name,
"tljh_repo2docker.display_name": name,
"tljh_repo2docker.mem_limit": memory,
"tljh_repo2docker.cpu_limit": cpu,
},
"Volumes": {
"/var/run/docker.sock": {
"bind": "/var/run/docker.sock",
"mode": "rw",
}
},
"HostConfig": {
"Binds": ["/var/run/docker.sock:/var/run/docker.sock"],
},
"Tty": False,
"AttachStdout": False,
"AttachStderr": False,
"OpenStdin": False,
}

if username and password:
config.update(
{
"Env": [f"GIT_CREDENTIAL_ENV=username={username}\npassword={password}"],
}
)

async with Docker() as docker:
await docker.containers.run(config=config)
6 changes: 3 additions & 3 deletions tljh_repo2docker/handlers/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .base import BaseHandler
from tornado import web

# from .docker import list_images
from .docker import list_images


class ServersHandler(BaseHandler):
Expand All @@ -14,8 +14,8 @@ class ServersHandler(BaseHandler):

@web.authenticated
async def get(self):
images = []#await list_images()
user = self.current_user
images = await list_images()
user = await self.fetch_user()
# if user.running:
# # trigger poll_and_notify event in case of a server that died
# await user.spawner.poll_and_notify()
Expand Down
2 changes: 2 additions & 0 deletions tljh_repo2docker/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ async def get(self):
named_spawners = user.all_spawners(include_default=False)
server_data = []
for sp in named_spawners:
print('#####', user, sp)
server_data.append(
self._spawner_to_server_data(sp, user)
)
print('AAAAAAa', server_data)
try:
named_server_limit = await self.get_current_user_named_server_limit()
except Exception:
Expand Down
15 changes: 15 additions & 0 deletions tljh_repo2docker/service_templates/admin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends "templates/admin.html" %}

{% block thead %}
<th>Image</th>
{{ super() }}
{% endblock thead %}

{% block user_row %}

{# TODO: move this td after the call to super() so it's at the end of the table #}
{# after this PR is merged and a new version released: https://github.com/jupyterhub/jupyterhub/pull/3015 #}
<td class="col-sm-2">{{ spawner.image }}</td>
{{ super() }}

{% endblock user_row %}
8 changes: 8 additions & 0 deletions tljh_repo2docker/service_templates/images.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "page.html" %} {% block main %}
<div id="environments-root">
<script id="tljh-page-data" type="application/json">
{"images": {{ images | tojson }}, "default_mem_limit": "{{default_mem_limit}}", "default_cpu_limit":"{{default_cpu_limit}}", "machine_profiles": {{ machine_profiles | tojson }}}
</script>
<script src="{{ base_url }}environments-static/js/react/environments.js"></script>
</div>
{% endblock %}
Loading

0 comments on commit 02aa466

Please sign in to comment.