Skip to content

Commit

Permalink
Start server work
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Apr 11, 2024
1 parent 4c14780 commit d20e69b
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 43 deletions.
3 changes: 2 additions & 1 deletion src/servers/NewServerDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ function _NewServerDialog(props: INewServerDialogProps) {
);

const createServer = useCallback(async () => {
const imageName = props.images[rowSelectionModel[0] as number].image_name;
const imageData = props.images[rowSelectionModel[0] as number];
const imageName = imageData.uid ?? imageData.image_name;
const data: { [key: string]: string } = {
imageName,
userName: jhData.user,
Expand Down
3 changes: 1 addition & 2 deletions tljh_repo2docker/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
from .binderhub_log import BinderHubLogsHandler
from .builder import BuildHandler
from .database.manager import ImagesDatabaseManager
from .dbutil import (async_session_context_factory, sync_to_async_url,
upgrade_if_needed)
from .dbutil import async_session_context_factory, sync_to_async_url, upgrade_if_needed
from .environments import EnvironmentsHandler
from .logs import LogsHandler
from .servers import ServersHandler
Expand Down
89 changes: 89 additions & 0 deletions tljh_repo2docker/base.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import functools
import json
import os
from contextlib import _AsyncGeneratorContextManager
from http.client import responses
from typing import Callable, Dict, List, Optional, Tuple

from httpx import AsyncClient
from jinja2 import Template
from jupyterhub.services.auth import HubOAuthenticated
from jupyterhub.utils import url_path_join
from sqlalchemy.ext.asyncio import AsyncSession
from tornado import web

from tljh_repo2docker import TLJH_R2D_ADMIN_SCOPE
from tljh_repo2docker.database.manager import ImagesDatabaseManager

from .model import UserModel

Expand Down Expand Up @@ -37,6 +41,9 @@ class BaseHandler(HubOAuthenticated, web.RequestHandler):

@property
def client(self):
"""
Get the asynchronous HTTP client with valid authorization token.
"""
if not BaseHandler._client:
api_url = os.environ.get("JUPYTERHUB_API_URL", "")
api_token = os.environ.get("JUPYTERHUB_API_TOKEN", None)
Expand Down Expand Up @@ -157,3 +164,85 @@ def write_error(self, status_code, **kwargs):
self.write(
json.dumps({"status": status_code, "message": message or status_message})
)

@property
def use_binderhub(self) -> bool:
"""
Check if BinderHub is being used by checking for the binderhub url
in the setting.
Returns:
bool: True if BinderHub is being used, False otherwise.
"""
return self.settings.get("binderhub_url", None) is not None

def get_db_handlers(
self,
) -> Tuple[
Optional[Callable[[], _AsyncGeneratorContextManager[AsyncSession]]],
Optional[ImagesDatabaseManager],
]:
"""
Get database handlers.
Returns the database context and image database manager based on the
configuration and settings. If `use_binderhub` flag is set to True,
returns the configured database context and image database manager;
otherwise, returns None for both.
Returns:
Tuple[Optional[Callable[[], _AsyncGeneratorContextManager[AsyncSession]]],
Optional[ImagesDatabaseManager]]: A tuple containing:
- The database context, which is a callable returning an
async generator context manager for session management.
- The image database manager, which handles image database
operations.
"""
if self.use_binderhub:
db_context = self.settings.get("db_context")
image_db_manager = self.settings.get("image_db_manager")
return db_context, image_db_manager
else:
return None, None

async def get_images_from_db(self) -> List[Dict]:
"""
Retrieve images from the database.
This method fetches image information from the database, formats it,
and returns a list of dictionaries representing each image.
Returns:
List[Dict]: A list of dictionaries, each containing information
about an image. Each dictionary has the following keys:
- image_name (str): The name of the docker image.
- uid (str): The unique identifier of the image.
- status (str): The build status of the image.
- display_name (str): The user defined name of the image.
- repo (str): Source repo used to build the image.
- ref (str): Commit reference.
- cpu_limit (str): CPU limit.
- mem_limit (str): Memory limit.
Note:
If `use_binderhub` flag is set to True and valid database context
and image database manager are available, it retrieves image
information; otherwise, an empty list is returned.
"""
db_context, image_db_manager = self.get_db_handlers()
all_images = []
if self.use_binderhub and db_context and image_db_manager:
async with db_context() as db:
docker_images = await image_db_manager.read_all(db)
all_images = [
dict(
image_name=image.name,
uid=str(image.uid),
status=image.status,
**image.image_meta.model_dump(),
)
for image in docker_images
]

return all_images
25 changes: 17 additions & 8 deletions tljh_repo2docker/binderhub_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
from tornado import web

from .base import BaseHandler, require_admin_role
from .database.manager import ImagesDatabaseManager
from .database.schemas import (BuildStatusType, DockerImageCreateSchema,
DockerImageUpdateSchema, ImageMetadataType)
from .database.schemas import (
BuildStatusType,
DockerImageCreateSchema,
DockerImageUpdateSchema,
ImageMetadataType,
)


class BinderHubBuildHandler(BaseHandler):
Expand All @@ -23,8 +26,10 @@ async def delete(self):
data = self.get_json_body()
uid = UUID(data["name"])

db_context = self.settings.get("db_context")
image_db_manager: ImagesDatabaseManager = self.settings.get("image_db_manager")
db_context, image_db_manager = self.get_db_handlers()
if not db_context or not image_db_manager:
return

deleted = False
async with db_context() as db:
image = await image_db_manager.read(db, uid)
Expand Down Expand Up @@ -60,8 +65,11 @@ async def post(self):
url = url_path_join(binder_url, "build", provider, quoted_repo, ref)

params = {"build_only": "true"}
db_context = self.settings.get("db_context")
image_db_manager: ImagesDatabaseManager = self.settings.get("image_db_manager")

db_context, image_db_manager = self.get_db_handlers()
if not db_context or not image_db_manager:
return

uid = uuid4()
image_in = DockerImageCreateSchema(
uid=uid,
Expand All @@ -85,7 +93,8 @@ async def post(self):
json_log = json.loads(line.split(":", 1)[1])
phase = json_log.get("phase", None)
message = json_log.get("message", "")
log += message
if phase != "unknown":
log += message
update_data = DockerImageUpdateSchema(uid=uid, log=log)
stop = False
if phase == "ready" or phase == "built":
Expand Down
7 changes: 4 additions & 3 deletions tljh_repo2docker/binderhub_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from tornado.iostream import StreamClosedError

from .base import BaseHandler, require_admin_role
from .database.manager import ImagesDatabaseManager
from .database.schemas import BuildStatusType


Expand All @@ -21,8 +20,10 @@ async def get(self, image_uid: str):
self.set_header("Content-Type", "text/event-stream")
self.set_header("Cache-Control", "no-cache")

db_context = self.settings.get("db_context")
image_db_manager: ImagesDatabaseManager = self.settings.get("image_db_manager")
db_context, image_db_manager = self.get_db_handlers()
if not db_context or not image_db_manager:
return

async with db_context() as db:

image = await image_db_manager.read(db, UUID(image_uid))
Expand Down
7 changes: 5 additions & 2 deletions tljh_repo2docker/database/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
from tornado.web import HTTPError

from .model import DockerImageSQL
from .schemas import (DockerImageCreateSchema, DockerImageOutSchema,
DockerImageUpdateSchema)
from .schemas import (
DockerImageCreateSchema,
DockerImageOutSchema,
DockerImageUpdateSchema,
)


class ImagesDatabaseManager:
Expand Down
3 changes: 1 addition & 2 deletions tljh_repo2docker/dbutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
import alembic.config
from alembic.script import ScriptDirectory
from sqlalchemy import create_engine, inspect, text
from sqlalchemy.ext.asyncio import (AsyncSession, async_sessionmaker,
create_async_engine)
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

HERE = Path(__file__).parent.resolve()
ALEMBIC_DIR = HERE / "alembic"
Expand Down
24 changes: 4 additions & 20 deletions tljh_repo2docker/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from tornado import web

from .base import BaseHandler, require_admin_role
from .database.manager import ImagesDatabaseManager
from .docker import list_containers, list_images


Expand All @@ -16,25 +15,10 @@ class EnvironmentsHandler(BaseHandler):
@require_admin_role
async def get(self):
all_images = []
if self.settings.get("binderhub_url", None):
db_context = self.settings.get("db_context")
image_db_manager: ImagesDatabaseManager = self.settings.get(
"image_db_manager"
)
async with db_context() as db:
docker_images = await image_db_manager.read_all(db)
all_images = [
dict(
image_name=image.name,
uid=str(image.uid),
status=image.status,
**image.image_meta.model_dump()
)
for image in docker_images
]
use_binderhub = True

if self.use_binderhub:
all_images = await self.get_images_from_db()
else:
use_binderhub = False
images = await list_images()
containers = await list_containers()
all_images = images + containers
Expand All @@ -46,7 +30,7 @@ async def get(self):
default_cpu_limit=self.settings.get("default_cpu_limit"),
machine_profiles=self.settings.get("machine_profiles", []),
repo_providers=self.settings.get("repo_providers", None),
use_binderhub=use_binderhub,
use_binderhub=self.use_binderhub,
)
if isawaitable(result):
self.write(await result)
Expand Down
10 changes: 9 additions & 1 deletion tljh_repo2docker/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ class ServersHandler(BaseHandler):

@web.authenticated
async def get(self):
images = await list_images()
images = []
if self.use_binderhub:
images = await self.get_images_from_db()
else:
try:
images = await list_images()
except ValueError:
pass

user_data = await self.fetch_user()

server_data = user_data.all_spawners()
Expand Down
20 changes: 18 additions & 2 deletions tljh_repo2docker/servers_api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from uuid import UUID

from jupyterhub.utils import url_path_join
from tornado import web

Expand All @@ -12,14 +14,28 @@ class ServersAPIHandler(BaseHandler):
@web.authenticated
async def post(self):
data = self.get_json_body()
image_name = data.get("imageName", None)

image_name_or_uid = data.get("imageName", None)
user_name = data.get("userName", None)
server_name = data.get("serverName", "")
if user_name != self.current_user["name"]:
raise web.HTTPError(403, "Unauthorized")
if not image_name:
if not image_name_or_uid:
raise web.HTTPError(400, "Missing image name")

if self.use_binderhub:
db_context, image_db_manager = self.get_db_handlers()
if not db_context or not image_db_manager:
raise web.HTTPError(500, "Server error, missing database")

async with db_context() as db:
image = await image_db_manager.read(db, UUID(image_name_or_uid))
if not image:
raise web.HTTPError(404, "Image not found")
image_name = image.name
else:
image_name = image_name_or_uid

post_data = {"image": image_name}

path = ""
Expand Down
9 changes: 7 additions & 2 deletions tljh_repo2docker/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
import json

from aiodocker import Docker, DockerError
from jupyterhub.tests.utils import (async_requests, auth_header,
check_db_locks, public_host, public_url)
from jupyterhub.tests.utils import (
async_requests,
auth_header,
check_db_locks,
public_host,
public_url,
)
from jupyterhub.utils import url_path_join as ujoin
from tornado.httputil import url_concat

Expand Down

0 comments on commit d20e69b

Please sign in to comment.