diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cf118d2..6a44687 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,6 @@ jobs: fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10"] - jupyterhub-version: [latest, 1.2.2] steps: - uses: actions/checkout@v2 @@ -32,10 +31,6 @@ jobs: python -m pip install --upgrade pip setuptools python -m pip install -r dev-requirements.txt python -m pip install -e . - - name: Downgrade jupyterhub - if: ${{ matrix.jupyterhub-version != 'latest' }} - run: | - python -m pip install -U 'jupyterhub==${{ matrix.jupyterhub-version }}' - name: Run Tests run: | python -m pytest --cov diff --git a/README.md b/README.md index d61fb3c..1c4cc94 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ TLJH plugin to build and use Docker images as user environments. The Docker images are built using [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/). +## Requirements + +This plugin requires [The Littlest JupyterHub](https://tljh.jupyter.org) 1.0 or later (running on JupyterHub 4+). + ## Installation During the [TLJH installation process](http://tljh.jupyter.org/en/latest/install/index.html), use the following post-installation script: @@ -20,9 +24,10 @@ sudo apt update && sudo apt install -y docker-ce # pull the repo2docker image sudo docker pull quay.io/jupyterhub/repo2docker:main -# install TLJH -curl https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/master/bootstrap/bootstrap.py \ +# install TLJH 1.0 +curl https://tljh.jupyter.org/bootstrap.py | sudo python3 - \ + --version 1.0.0 \ --admin test:test \ --plugin git+https://github.com/plasmabio/tljh-repo2docker@master ``` @@ -30,7 +35,6 @@ curl https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/master Refer to [The Littlest JupyterHub documentation](http://tljh.jupyter.org/en/latest/topic/customizing-installer.html?highlight=plugins#installing-tljh-plugins) for more info on installing TLJH plugins. - ## Usage ### List the environments diff --git a/dev-requirements.txt b/dev-requirements.txt index 4a0a7e6..5a9414a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,8 +1,8 @@ -git+https://github.com/jupyterhub/the-littlest-jupyterhub -jupyterhub~=1.5 -notebook<7 +git+https://github.com/jupyterhub/the-littlest-jupyterhub@1.0.0 +jupyterhub>=4,<5 pytest pytest-aiohttp pytest-asyncio pytest-cov +pytest-jupyterhub requests-mock diff --git a/pyproject.toml b/pyproject.toml index cfcdd81..4436659 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,8 +10,7 @@ license = { file = "LICENSE" } dependencies = [ "aiodocker~=0.19", "dockerspawner~=12.1", - "jupyter_client~=6.1", - "sqlalchemy<2", + "jupyter_client>=6.1,<8" ] [project.entry-points.tljh] diff --git a/pytest.ini b/pytest.ini index 0ee949b..faf59a4 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,3 @@ [pytest] python_files = test_*.py +asyncio_mode = auto \ No newline at end of file diff --git a/setup.py b/setup.py index 54ce4b7..aefdf20 100644 --- a/setup.py +++ b/setup.py @@ -1 +1 @@ -__import__("setuptools").setup() \ No newline at end of file +__import__("setuptools").setup() diff --git a/tljh_repo2docker/builder.py b/tljh_repo2docker/builder.py index fbef6d3..083b799 100644 --- a/tljh_repo2docker/builder.py +++ b/tljh_repo2docker/builder.py @@ -3,7 +3,7 @@ from aiodocker import Docker, DockerError from jupyterhub.apihandlers import APIHandler -from jupyterhub.utils import admin_only +from jupyterhub.scopes import needs_scope from tornado import web from .docker import build_image @@ -17,7 +17,7 @@ class BuildHandler(APIHandler): """ @web.authenticated - @admin_only + @needs_scope('admin-ui') async def delete(self): data = self.get_json_body() name = data["name"] @@ -31,7 +31,7 @@ async def delete(self): self.finish(json.dumps({"status": "ok"})) @web.authenticated - @admin_only + @needs_scope('admin-ui') async def post(self): data = self.get_json_body() repo = data["repo"] diff --git a/tljh_repo2docker/images.py b/tljh_repo2docker/images.py index df1be13..ddceeb3 100644 --- a/tljh_repo2docker/images.py +++ b/tljh_repo2docker/images.py @@ -1,6 +1,6 @@ from inspect import isawaitable from jupyterhub.handlers.base import BaseHandler -from jupyterhub.utils import admin_only +from jupyterhub.scopes import needs_scope from tornado import web from .docker import list_containers, list_images @@ -12,7 +12,7 @@ class ImagesHandler(BaseHandler): """ @web.authenticated - @admin_only + @needs_scope('admin-ui') async def get(self): images = await list_images() containers = await list_containers() diff --git a/tljh_repo2docker/logs.py b/tljh_repo2docker/logs.py index e431412..7c87e16 100644 --- a/tljh_repo2docker/logs.py +++ b/tljh_repo2docker/logs.py @@ -2,9 +2,8 @@ from aiodocker import Docker from jupyterhub.apihandlers import APIHandler -from jupyterhub.utils import admin_only +from jupyterhub.scopes import needs_scope from tornado import web -from tornado.ioloop import IOLoop from tornado.iostream import StreamClosedError @@ -13,7 +12,7 @@ class LogsHandler(APIHandler): Expose a handler to follow the build logs. """ @web.authenticated - @admin_only + @needs_scope('admin-ui') async def get(self, name): self.set_header("Content-Type", "text/event-stream") self.set_header("Cache-Control", "no-cache") diff --git a/tljh_repo2docker/tests/conftest.py b/tljh_repo2docker/tests/conftest.py index 1155930..5b7ee87 100644 --- a/tljh_repo2docker/tests/conftest.py +++ b/tljh_repo2docker/tests/conftest.py @@ -1,25 +1,8 @@ -import asyncio -import os -import sys - import pytest from aiodocker import Docker, DockerError -from jupyterhub.tests.conftest import ( - io_loop, - event_loop, - db, - pytest_collection_modifyitems, -) -from jupyterhub.tests.mocking import MockHub from tljh_repo2docker import tljh_custom_jupyterhub_config -from traitlets import Bunch - -class DummyConfig: - def __getattr__(self, k): - if k not in self.__dict__: - self.__dict__[k] = Bunch() - return self.__dict__[k] +from traitlets.config import Config async def remove_docker_image(image_name): @@ -50,43 +33,17 @@ def image_name(): return "tljh-repo2docker-test:HEAD" -@pytest.fixture(scope='module') -def app(request, io_loop): - """ - Adapted from: - https://github.com/jupyterhub/jupyterhub/blob/8a3790b01ff944c453ffcc0486149e2a58ffabea/jupyterhub/tests/conftest.py#L74 - """ - - mocked_app = MockHub.instance() - c = DummyConfig() - c.JupyterHub = mocked_app - tljh_custom_jupyterhub_config(c) - - async def make_app(): - await mocked_app.initialize([]) - await mocked_app.start() - - def fin(): - # disconnect logging during cleanup because pytest closes captured FDs prematurely - mocked_app.log.handlers = [] - MockHub.clear_instance() - try: - mocked_app.stop() - except Exception as e: - print("Error stopping Hub: %s" % e, file=sys.stderr) +@pytest.fixture +async def app(hub_app): + config = Config() + tljh_custom_jupyterhub_config(config) - request.addfinalizer(fin) - io_loop.run_sync(make_app) - return mocked_app + app = await hub_app(config=config) + return app @pytest.fixture(autouse=True) -def remove_all_test_images(image_name, generated_image_name, io_loop): - try: - yield - finally: - async def _clean(): - await remove_docker_image(image_name) - await remove_docker_image(generated_image_name) - - io_loop.run_sync(_clean) +async def remove_all_test_images(image_name, generated_image_name, app): + yield + await remove_docker_image(image_name) + await remove_docker_image(generated_image_name)