Skip to content

Commit

Permalink
Switch to service
Browse files Browse the repository at this point in the history
  • Loading branch information
trungleduc committed Mar 15, 2024
1 parent e5ed825 commit 7da5492
Show file tree
Hide file tree
Showing 35 changed files with 804 additions and 206 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ ui-tests/test-results
lib/

# Hatch version
_version.py
_version.py
57 changes: 52 additions & 5 deletions jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
and overrides some of the default values from the plugin.
"""

import getpass

from jupyterhub.auth import DummyAuthenticator
from tljh.configurer import apply_config, load_config
from tljh_repo2docker import tljh_custom_jupyterhub_config
from tljh_repo2docker import tljh_custom_jupyterhub_config, TLJH_R2D_ADMIN_SCOPE
import sys

c.JupyterHub.services = []

Expand All @@ -30,7 +29,55 @@

c.JupyterHub.authenticator_class = DummyAuthenticator

user = getpass.getuser()
c.Authenticator.admin_users = {user, "alice"}
c.JupyterHub.allow_named_servers = True
c.JupyterHub.ip = "0.0.0.0"

c.JupyterHub.services.extend(
[
{
"name": "tljh_repo2docker",
"url": "http://127.0.0.1:6789",
"command": [
sys.executable,
"-m",
"tljh_repo2docker",
"--ip",
"127.0.0.1",
"--port",
"6789",
],
"oauth_no_confirm": True,
"oauth_client_allowed_scopes": [
TLJH_R2D_ADMIN_SCOPE,
],
}
]
)

c.JupyterHub.custom_scopes = {
"custom:tljh_repo2docker:admin": {
"description": "Admin access to myservice",
},
}

c.JupyterHub.load_roles = [
{
"description": "Role for tljh_repo2docker service",
"name": "tljh-repo2docker-service",
"scopes": ["read:users", "read:servers", "read:roles:users"],
"services": ["tljh_repo2docker"],
},
{
"name": 'tljh-repo2docker-service-admin',
"users": ["alice"],
"scopes": [TLJH_R2D_ADMIN_SCOPE],
},
{
"name": "user",
"scopes": [
"self",
# access to the env page
"access:services!service=tljh_repo2docker",
],
},
]
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies = [
"aiodocker~=0.19",
"dockerspawner~=12.1",
"jupyter_client>=6.1,<8",
"httpx"
]
dynamic = ["version"]
license = {file = "LICENSE"}
Expand Down
5 changes: 4 additions & 1 deletion src/common/AxiosContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createContext, useContext } from 'react';
import { AxiosClient } from './axiosclient';

export const AxiosContext = createContext<AxiosClient>(new AxiosClient({}));
export const AxiosContext = createContext<{
hubClient: AxiosClient;
serviceClient: AxiosClient;
}>({ hubClient: new AxiosClient({}), serviceClient: new AxiosClient({}) });

export const useAxios = () => {
return useContext(AxiosContext);
Expand Down
6 changes: 4 additions & 2 deletions src/common/JupyterhubContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { createContext, useContext } from 'react';

export interface IJupyterhubData {
baseUrl: string;
prefix: string;
servicePrefix: string;
hubPrefix: string;
user: string;
adminAccess: boolean;
xsrfToken: string;
}
export const JupyterhubContext = createContext<IJupyterhubData>({
baseUrl: '',
prefix: '',
servicePrefix: '',
hubPrefix: '',
user: '',
adminAccess: false,
xsrfToken: ''
Expand Down
14 changes: 11 additions & 3 deletions src/environments/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@ export interface IAppProps {
}
export default function App(props: IAppProps) {
const jhData = useJupyterhub();
const axios = useMemo(() => {
const baseUrl = jhData.baseUrl;

const hubClient = useMemo(() => {
const baseUrl = jhData.hubPrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

const serviceClient = useMemo(() => {
const baseUrl = jhData.servicePrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

return (
<ThemeProvider theme={customTheme}>
<AxiosContext.Provider value={axios}>
<AxiosContext.Provider value={{ hubClient, serviceClient }}>
<ScopedCssBaseline>
<Stack sx={{ padding: 1 }} spacing={1}>
<NewEnvironmentDialog
Expand Down
4 changes: 2 additions & 2 deletions src/environments/LogDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ function _EnvironmentLogButton(props: IEnvironmentLogButton) {

terminal.open(divRef.current);
fitAddon.fit();
const { baseUrl, xsrfToken } = jhData;
const { servicePrefix, xsrfToken } = jhData;

let logsUrl = urlJoin(
baseUrl,
servicePrefix,
'api',
'environments',
props.image,
Expand Down
2 changes: 1 addition & 1 deletion src/environments/NewEnvironmentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function _NewEnvironmentDialog(props: INewEnvironmentDialogProps) {
data.memory = data.memory ?? '2';
data.username = data.username ?? '';
data.password = data.password ?? '';
const response = await axios.request({
const response = await axios.serviceClient.request({
method: 'post',
prefix: API_PREFIX,
path: ENV_PREFIX,
Expand Down
2 changes: 1 addition & 1 deletion src/environments/RemoveEnvironmentButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function _RemoveEnvironmentButton(props: IRemoveEnvironmentButton) {
const axios = useAxios();

const removeEnv = useCallback(async () => {
const response = await axios.request({
const response = await axios.serviceClient.request({
method: 'delete',
prefix: API_PREFIX,
path: ENV_PREFIX,
Expand Down
13 changes: 10 additions & 3 deletions src/environments/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,22 @@ if (rootElement) {
configData = JSON.parse(dataElement.textContent || '') as IAppProps;
}
const jhData = (window as any).jhdata;
const { base_url, xsrf_token, user, prefix, admin_access } = jhData;

const {
base_url,
xsrf_token,
user,
hub_prefix,
service_prefix,
admin_access
} = jhData;
root.render(
<JupyterhubContext.Provider
value={{
baseUrl: base_url,
xsrfToken: xsrf_token,
user,
prefix,
hubPrefix: hub_prefix ?? base_url,
servicePrefix: service_prefix ?? base_url,
adminAccess: admin_access
}}
>
Expand Down
13 changes: 10 additions & 3 deletions src/servers/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@ export interface IAppProps {
}
export default function App(props: IAppProps) {
const jhData = useJupyterhub();
const axios = useMemo(() => {
const baseUrl = jhData.baseUrl;

const hubClient = useMemo(() => {
const baseUrl = jhData.hubPrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

const serviceClient = useMemo(() => {
const baseUrl = jhData.servicePrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

return (
<ThemeProvider theme={customTheme}>
<AxiosContext.Provider value={axios}>
<AxiosContext.Provider value={{ hubClient, serviceClient }}>
<ScopedCssBaseline>
<Stack sx={{ padding: 1 }} spacing={1}>
<NewServerDialog
Expand Down
2 changes: 1 addition & 1 deletion src/servers/NewServerDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function _NewServerDialog(props: INewServerDialogProps) {
path = jhData.user;
}
try {
await axios.request({
await axios.hubClient.request({
method: 'post',
prefix: SPAWN_PREFIX,
path,
Expand Down
6 changes: 3 additions & 3 deletions src/servers/OpenServerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ function _OpenServerButton(props: IOpenServerButton) {

const [progress, setProgress] = useState(0);
useEffect(() => {
const { user, baseUrl, xsrfToken } = jhData;
const { user, hubPrefix, xsrfToken } = jhData;
let progressUrl = urlJoin(
baseUrl,
hubPrefix,
'api',
'users',
user,
Expand Down Expand Up @@ -54,7 +54,7 @@ function _OpenServerButton(props: IOpenServerButton) {
const data = new FormData();
data.append('image', imageName);
try {
await axios.request({
await axios.hubClient.request({
method: 'post',
prefix: SPAWN_PREFIX,
path: `${jhData.user}/${props.serverName}`,
Expand Down
2 changes: 1 addition & 1 deletion src/servers/RemoveServerButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function _RemoveServerButton(props: IRemoveServerButton) {
path = `users/${jhData.user}/server`;
}
try {
await axios.request({
await axios.hubClient.request({
method: 'delete',
prefix: API_PREFIX,
path,
Expand Down
12 changes: 10 additions & 2 deletions src/servers/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,22 @@ if (rootElement) {
configData = JSON.parse(dataElement.textContent || '') as IAppProps;
}
const jhData = (window as any).jhdata;
const { base_url, xsrf_token, user, prefix, admin_access } = jhData;
const {
base_url,
xsrf_token,
user,
hub_prefix,
service_prefix,
admin_access
} = jhData;
root.render(
<JupyterhubContext.Provider
value={{
baseUrl: base_url,
xsrfToken: xsrf_token,
user,
prefix,
hubPrefix: hub_prefix ?? base_url,
servicePrefix: service_prefix ?? base_url,
adminAccess: admin_access
}}
>
Expand Down
41 changes: 6 additions & 35 deletions tljh_repo2docker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import os
from typing import Any, Coroutine, Optional

from aiodocker import Docker
from dockerspawner import DockerSpawner
from jinja2 import Environment, BaseLoader
from jinja2 import BaseLoader, Environment
from jupyter_client.localinterfaces import public_ips
from jupyterhub.handlers.static import CacheControlStaticFilesHandler
from jupyterhub.traitlets import ByteSpecification
from tljh.hooks import hookimpl
from tljh.configurer import load_config
from tljh.hooks import hookimpl
from traitlets import Unicode
from traitlets.config import Configurable

from .builder import BuildHandler
from .docker import list_images
from .servers import ServersHandler
from .images import ImagesHandler
from .logs import LogsHandler

# 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

TLJH_R2D_ADMIN_SCOPE = "custom:tljh_repo2docker:admin"

class SpawnerMixin(Configurable):

class SpawnerMixin(Configurable):
"""
Mixin for spawners that derive from DockerSpawner, to use local Docker images
built with tljh-repo2docker.
Expand Down Expand Up @@ -175,11 +168,6 @@ def tljh_custom_jupyterhub_config(c):
c.JupyterHub.cleanup_servers = False
c.JupyterHub.spawner_class = Repo2DockerSpawner

# add extra templates for the service UI
c.JupyterHub.template_paths.insert(
0, os.path.join(os.path.dirname(__file__), "templates")
)

# spawner
c.DockerSpawner.cmd = ["jupyterhub-singleuser"]
c.DockerSpawner.pull_policy = "Never"
Expand All @@ -197,26 +185,9 @@ def tljh_custom_jupyterhub_config(c):

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

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

# register the handlers to manage the user images
c.JupyterHub.extra_handlers.extend(
[
(r"servers", ServersHandler),
(r"environments", ImagesHandler),
(r"api/environments", BuildHandler),
(r"api/environments/([^/]+)/logs", LogsHandler),
(
r"environments-static/(.*)",
CacheControlStaticFilesHandler,
{"path": os.path.join(os.path.dirname(__file__), "static")},
),
]
)
c.JupyterHub.tornado_settings.update({"machine_profiles": machine_profiles})


@hookimpl
def tljh_extra_hub_pip_packages():
return ["dockerspawner~=0.11", "jupyter_client~=6.1", "aiodocker~=0.19"]
return ["dockerspawner~=0.11", "jupyter_client>=6.1,<8", "aiodocker~=0.19"]
4 changes: 4 additions & 0 deletions tljh_repo2docker/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
if __name__ == "__main__":
from .app import main

main()
Loading

0 comments on commit 7da5492

Please sign in to comment.