mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-15 14:03:02 +00:00
Merge branch 'jupyterhub:main' into contributing
This commit is contained in:
23
.github/workflows/test.yml
vendored
23
.github/workflows/test.yml
vendored
@@ -28,7 +28,6 @@ on:
|
||||
env:
|
||||
# UTF-8 content may be interpreted as ascii and causes errors without this.
|
||||
LANG: C.UTF-8
|
||||
PYTEST_ADDOPTS: "--verbose --color=yes"
|
||||
SQLALCHEMY_WARN_20: "1"
|
||||
|
||||
permissions:
|
||||
@@ -79,21 +78,30 @@ jobs:
|
||||
oldest_dependencies: oldest_dependencies
|
||||
legacy_notebook: legacy_notebook
|
||||
- python: "3.8"
|
||||
legacy_notebook: legacy_notebook
|
||||
jupyter_server: "1.*"
|
||||
subset: singleuser
|
||||
- python: "3.9"
|
||||
db: mysql
|
||||
- python: "3.10"
|
||||
db: postgres
|
||||
- python: "3.11"
|
||||
subdomain: subdomain
|
||||
serverextension: serverextension
|
||||
- python: "3.11"
|
||||
ssl: ssl
|
||||
serverextension: serverextension
|
||||
- python: "3.11"
|
||||
subdomain: subdomain
|
||||
noextension: noextension
|
||||
subset: singleuser
|
||||
- python: "3.11"
|
||||
ssl: ssl
|
||||
noextension: noextension
|
||||
subset: singleuser
|
||||
- python: "3.11"
|
||||
selenium: selenium
|
||||
- python: "3.11"
|
||||
main_dependencies: main_dependencies
|
||||
- python: "3.10"
|
||||
serverextension: serverextension
|
||||
|
||||
steps:
|
||||
# NOTE: In GitHub workflows, environment variables are set by writing
|
||||
@@ -119,6 +127,8 @@ jobs:
|
||||
fi
|
||||
if [ "${{ matrix.serverextension }}" != "" ]; then
|
||||
echo "JUPYTERHUB_SINGLEUSER_EXTENSION=1" >> $GITHUB_ENV
|
||||
elif [ "${{ matrix.noextension }}" != "" ]; then
|
||||
echo "JUPYTERHUB_SINGLEUSER_EXTENSION=0" >> $GITHUB_ENV
|
||||
fi
|
||||
- uses: actions/checkout@v3
|
||||
# NOTE: actions/setup-node@v3 make use of a cache within the GitHub base
|
||||
@@ -161,6 +171,9 @@ jobs:
|
||||
pip uninstall jupyter_server --yes
|
||||
pip install 'notebook<7'
|
||||
fi
|
||||
if [ "${{ matrix.jupyter_server }}" != "" ]; then
|
||||
pip install "jupyter_server==${{ matrix.jupyter_server }}"
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
pip install mysql-connector-python
|
||||
fi
|
||||
@@ -220,7 +233,7 @@ jobs:
|
||||
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest --maxfail=2 --cov=jupyterhub jupyterhub/tests
|
||||
pytest -k "${{ matrix.subset }}" --maxfail=2 --cov=jupyterhub jupyterhub/tests
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
|
||||
|
@@ -105,11 +105,16 @@ def _scrub_headers(headers):
|
||||
auth_type = ''
|
||||
headers['Authorization'] = f'{auth_type} [secret]'
|
||||
if 'Cookie' in headers:
|
||||
c = SimpleCookie(headers['Cookie'])
|
||||
redacted = []
|
||||
for name in c.keys():
|
||||
redacted.append(f"{name}=[secret]")
|
||||
headers['Cookie'] = '; '.join(redacted)
|
||||
try:
|
||||
c = SimpleCookie(headers['Cookie'])
|
||||
except Exception as e:
|
||||
# it's possible for browsers to send invalid cookies
|
||||
headers['Cookie'] = f"Invalid Cookie: {e}"
|
||||
else:
|
||||
redacted = []
|
||||
for name in c.keys():
|
||||
redacted.append(f"{name}=[secret]")
|
||||
headers['Cookie'] = '; '.join(redacted)
|
||||
return headers
|
||||
|
||||
|
||||
|
@@ -1,40 +1,75 @@
|
||||
"""JupyterHub single-user server entrypoints
|
||||
|
||||
Contains default notebook-app subclass and mixins
|
||||
|
||||
Defaults to:
|
||||
|
||||
- Jupyter server extension with Jupyter Server >=2
|
||||
- Subclass with Jupyter Server <2 or clasic notebook
|
||||
|
||||
Application subclass can be controlled with environment variables:
|
||||
|
||||
- JUPYTERHUB_SINGLEUSER_EXTENSION=1 to opt-in to the extension (requires Jupyter Server 2)
|
||||
- JUPYTERHUB_SINGLEUSER_APP=notebook (or jupyter-server) to opt-in
|
||||
"""
|
||||
import os
|
||||
|
||||
from .mixins import HubAuthenticatedHandler, make_singleuser_app
|
||||
|
||||
if os.environ.get("JUPYTERHUB_SINGLEUSER_EXTENSION", "") not in ("", "0"):
|
||||
_as_extension = False
|
||||
_extension_env = os.environ.get("JUPYTERHUB_SINGLEUSER_EXTENSION", "")
|
||||
_app_env = os.environ.get("JUPYTERHUB_SINGLEUSER_APP", "")
|
||||
|
||||
if not _extension_env:
|
||||
# extension env not set, check app env
|
||||
if not _app_env or 'jupyter_server' in _app_env.replace("-", "_"):
|
||||
# no app env set or using jupyter-server, this is the default branch
|
||||
# default behavior:
|
||||
# - extension, if jupyter server 2
|
||||
# - older subclass app, otherwise
|
||||
try:
|
||||
import jupyter_server
|
||||
|
||||
_server_major = int(jupyter_server.__version__.split(".", 1)[0])
|
||||
except Exception:
|
||||
# don't have jupyter-server, assume classic notebook
|
||||
_as_extension = False
|
||||
else:
|
||||
# default to extension if jupyter-server >=2
|
||||
_as_extension = _server_major >= 2
|
||||
|
||||
elif _app_env == "extension":
|
||||
_as_extension = True
|
||||
else:
|
||||
# app env set and not to jupyter-server, that opts out of extension
|
||||
_as_extension = False
|
||||
elif _extension_env == "0":
|
||||
_as_extension = False
|
||||
else:
|
||||
# extension env set to anything non-empty other than '0' enables the extension
|
||||
_as_extension = True
|
||||
|
||||
if _as_extension:
|
||||
# check for conflict in singleuser entrypoint environment variables
|
||||
if os.environ.get("JUPYTERHUB_SINGLEUSER_APP", "") not in {
|
||||
if _app_env not in {
|
||||
"",
|
||||
"jupyter_server",
|
||||
"jupyter-server",
|
||||
"extension",
|
||||
"jupyter_server.serverapp.ServerApp",
|
||||
}:
|
||||
ext = os.environ["JUPYTERHUB_SINGLEUSER_EXTENSION"]
|
||||
app = os.environ["JUPYTERHUB_SINGLEUSER_APP"]
|
||||
raise ValueError(
|
||||
f"Cannot use JUPYTERHUB_SINGLEUSER_EXTENSION={ext} with JUPYTERHUB_SINGLEUSER_APP={app}."
|
||||
f"Cannot use JUPYTERHUB_SINGLEUSER_EXTENSION={_extension_env} with JUPYTERHUB_SINGLEUSER_APP={_app_env}."
|
||||
" Please pick one or the other."
|
||||
)
|
||||
from .extension import main
|
||||
else:
|
||||
_as_extension = False
|
||||
try:
|
||||
from .app import SingleUserNotebookApp, main
|
||||
except ImportError:
|
||||
# check for Jupyter Server 2.0 ?
|
||||
from .extension import main
|
||||
else:
|
||||
# backward-compatibility
|
||||
JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class
|
||||
JupyterHubLogoutHandler = SingleUserNotebookApp.logout_handler_class
|
||||
OAuthCallbackHandler = SingleUserNotebookApp.oauth_callback_handler_class
|
||||
from .app import SingleUserNotebookApp, main
|
||||
|
||||
# backward-compatibility
|
||||
JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class
|
||||
JupyterHubLogoutHandler = SingleUserNotebookApp.logout_handler_class
|
||||
OAuthCallbackHandler = SingleUserNotebookApp.oauth_callback_handler_class
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@@ -475,6 +475,7 @@ class JupyterHubSingleUser(ExtensionApp):
|
||||
cfg.identity_provider_class = JupyterHubIdentityProvider
|
||||
|
||||
# disable some single-user features
|
||||
cfg.allow_remote_access = True
|
||||
cfg.open_browser = False
|
||||
cfg.trust_xheaders = True
|
||||
cfg.quit_button = False
|
||||
@@ -501,6 +502,13 @@ class JupyterHubSingleUser(ExtensionApp):
|
||||
# to make sure it has the desired effect
|
||||
cfg.default_url = self.default_url = self.get_default_url()
|
||||
|
||||
# load internal SSL configuration
|
||||
cfg.keyfile = os.environ.get('JUPYTERHUB_SSL_KEYFILE') or ''
|
||||
cfg.certfile = os.environ.get('JUPYTERHUB_SSL_CERTFILE') or ''
|
||||
cfg.client_ca = os.environ.get('JUPYTERHUB_SSL_CLIENT_CA') or ''
|
||||
if cfg.certfile:
|
||||
self.serverapp.log.info(f"Using SSL cert {cfg.certfile}")
|
||||
|
||||
# Jupyter Server default: config files have higher priority than extensions,
|
||||
# by:
|
||||
# 1. load config files
|
||||
|
@@ -660,6 +660,7 @@ class SingleUserNotebookAppMixin(Configurable):
|
||||
certfile=self.certfile,
|
||||
client_ca=self.client_ca,
|
||||
)
|
||||
self.hub_host = self.hub_auth.hub_host
|
||||
# smoke check
|
||||
if not self.hub_auth.oauth_client_id:
|
||||
raise ValueError("Missing OAuth client ID")
|
||||
|
@@ -283,4 +283,8 @@ async def test_nbclassic_control_panel(app, user, full_spawn):
|
||||
page = BeautifulSoup(r.text, "html.parser")
|
||||
link = page.find("a", id="jupyterhub-control-panel-link")
|
||||
assert link, f"Missing jupyterhub-control-panel-link in {page}"
|
||||
assert link["href"] == url_path_join(app.base_url, "hub/home")
|
||||
if app.subdomain_host:
|
||||
prefix = public_url(app)
|
||||
else:
|
||||
prefix = app.base_url
|
||||
assert link["href"] == url_path_join(prefix, "hub/home")
|
||||
|
@@ -7,7 +7,7 @@
|
||||
asyncio_mode = auto
|
||||
|
||||
# jupyter_server plugin is incompatible with notebook imports
|
||||
addopts = -p no:jupyter_server -m 'not selenium'
|
||||
addopts = -p no:jupyter_server -m 'not selenium' --color yes --durations 10 --verbose
|
||||
|
||||
python_files = test_*.py
|
||||
markers =
|
||||
|
Reference in New Issue
Block a user