mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 18:14:10 +00:00
Compare commits
85 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
10e7ab96e5 | ||
![]() |
40f519544f | ||
![]() |
076c14dce6 | ||
![]() |
e223ce59e1 | ||
![]() |
ad833755e1 | ||
![]() |
142978b4d8 | ||
![]() |
e3cab48039 | ||
![]() |
203f4a5855 | ||
![]() |
cfc27db43d | ||
![]() |
e2a8557083 | ||
![]() |
d5478b1f21 | ||
![]() |
cf19af6f1c | ||
![]() |
1342f00d8e | ||
![]() |
1e49b4379b | ||
![]() |
a5d563217c | ||
![]() |
b1ac3b82dc | ||
![]() |
75ebe40f86 | ||
![]() |
69d711929a | ||
![]() |
4c12872dbf | ||
![]() |
21cee1be31 | ||
![]() |
00c782fd40 | ||
![]() |
b3f9635ecc | ||
![]() |
8c10fb285e | ||
![]() |
8a3f5d8f2e | ||
![]() |
7b496a5b4a | ||
![]() |
64e7705053 | ||
![]() |
6502b50576 | ||
![]() |
861347cce0 | ||
![]() |
43d4b65250 | ||
![]() |
e53ce19fcc | ||
![]() |
e603ff8274 | ||
![]() |
22b15f0ecf | ||
![]() |
c48c5bce99 | ||
![]() |
fa11d7e3c6 | ||
![]() |
7e3f29d033 | ||
![]() |
b7827687a8 | ||
![]() |
0beb4639a3 | ||
![]() |
b010c9501e | ||
![]() |
295e92270b | ||
![]() |
e42066f1c9 | ||
![]() |
1d29fcbfb2 | ||
![]() |
bdbfbb7e32 | ||
![]() |
42314ed75b | ||
![]() |
d8141692ab | ||
![]() |
025db2f9f3 | ||
![]() |
3985140377 | ||
![]() |
6886384ca3 | ||
![]() |
4a7fe8648a | ||
![]() |
7383c0cf60 | ||
![]() |
83186e02a2 | ||
![]() |
c6b4577c0a | ||
![]() |
73b1922c17 | ||
![]() |
1430e02fa8 | ||
![]() |
9ef09a288a | ||
![]() |
4a093be938 | ||
![]() |
64a253dbef | ||
![]() |
54877025ca | ||
![]() |
7793176b65 | ||
![]() |
bf32599d5d | ||
![]() |
01a31c894c | ||
![]() |
1e9cf23302 | ||
![]() |
555969141e | ||
![]() |
a938982bdc | ||
![]() |
17b54fee6a | ||
![]() |
60a153718d | ||
![]() |
9e1e382c37 | ||
![]() |
d72a96ec17 | ||
![]() |
5f845e78f1 | ||
![]() |
0d7e608a64 | ||
![]() |
15c5f152f8 | ||
![]() |
6d13893f16 | ||
![]() |
7e35de2577 | ||
![]() |
ec78503d1e | ||
![]() |
7d0bc1a112 | ||
![]() |
98e4531b44 | ||
![]() |
bb92058fbf | ||
![]() |
a5c59d6550 | ||
![]() |
f14be3df65 | ||
![]() |
3f7a32c990 | ||
![]() |
a8d8fc02e7 | ||
![]() |
0713fa209e | ||
![]() |
850f430ad6 | ||
![]() |
4026ed87e8 | ||
![]() |
f57d196e33 | ||
![]() |
ca9dc3a179 |
@@ -1,4 +1,4 @@
|
||||
# dependabot.yml reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
# dependabot.yaml reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
#
|
||||
# Notes:
|
||||
# - Status and logs from dependabot are provided at
|
||||
@@ -8,8 +8,9 @@ version: 2
|
||||
updates:
|
||||
# Maintain dependencies in our GitHub Workflows
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
directory: /
|
||||
labels: [ci]
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: monthly
|
||||
time: "05:00"
|
||||
timezone: "Etc/UTC"
|
||||
timezone: Etc/UTC
|
33
.github/workflows/test-docs.yml
vendored
33
.github/workflows/test-docs.yml
vendored
@@ -49,6 +49,11 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# make rediraffecheckdiff requires git history to compare current
|
||||
# commit with the main branch and previous releases.
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
@@ -72,3 +77,31 @@ jobs:
|
||||
run: |
|
||||
cd docs
|
||||
make linkcheck
|
||||
|
||||
# make rediraffecheckdiff compares files for different changesets
|
||||
# these diff targets aren't always available
|
||||
# - compare with base ref (usually 'main', always on 'origin') for pull requests
|
||||
# - only compare with tags when running against jupyterhub/jupyterhub
|
||||
# to avoid errors on forks, which often lack tags
|
||||
- name: check redirects for this PR
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=origin/${{ github.base_ref }}
|
||||
make rediraffecheckdiff
|
||||
|
||||
# this should check currently published 'stable' links for redirects
|
||||
- name: check redirects since last release
|
||||
if: github.repository == 'jupyterhub/jupyterhub'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=$(git describe --tags --abbrev=0)
|
||||
make rediraffecheckdiff
|
||||
|
||||
# longer-term redirect check (fixed version) for older links
|
||||
- name: check redirects since 3.0.0
|
||||
if: github.repository == 'jupyterhub/jupyterhub'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=3.0.0
|
||||
make rediraffecheckdiff
|
||||
|
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
echo "MYSQL_HOST=127.0.0.1" >> $GITHUB_ENV
|
||||
echo "JUPYTERHUB_TEST_DB_URL=mysql+mysqlconnector://root@127.0.0.1:3306/jupyterhub" >> $GITHUB_ENV
|
||||
echo "JUPYTERHUB_TEST_DB_URL=mysql+mysqldb://root@127.0.0.1:3306/jupyterhub" >> $GITHUB_ENV
|
||||
fi
|
||||
if [ "${{ matrix.ssl }}" == "ssl" ]; then
|
||||
echo "SSL_ENABLED=1" >> $GITHUB_ENV
|
||||
@@ -164,7 +164,9 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
||||
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||
# Tests are broken:
|
||||
# https://github.com/jupyterhub/jupyterhub/issues/4418
|
||||
# pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||
pip install --upgrade --pre sqlalchemy
|
||||
fi
|
||||
if [ "${{ matrix.legacy_notebook }}" != "" ]; then
|
||||
@@ -175,7 +177,7 @@ jobs:
|
||||
pip install "jupyter_server==${{ matrix.jupyter_server }}"
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
pip install mysql-connector-python
|
||||
pip install mysqlclient
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||
pip install psycopg2-binary
|
||||
@@ -246,7 +248,7 @@ jobs:
|
||||
|
||||
- name: build images
|
||||
run: |
|
||||
docker build -t jupyterhub/jupyterhub .
|
||||
DOCKER_BUILDKIT=1 docker build -t jupyterhub/jupyterhub .
|
||||
docker build -t jupyterhub/jupyterhub-onbuild onbuild
|
||||
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
||||
docker build -t jupyterhub/singleuser singleuser
|
||||
|
@@ -24,7 +24,7 @@ repos:
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.0.1
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: autoflake
|
||||
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
||||
@@ -39,13 +39,13 @@ repos:
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
# Autoformat: markdown, yaml, javascript (see the file .prettierignore)
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0-alpha.4
|
||||
rev: v3.0.0-alpha.6
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
|
94
Dockerfile
94
Dockerfile
@@ -24,11 +24,11 @@
|
||||
ARG BASE_IMAGE=ubuntu:22.04
|
||||
FROM $BASE_IMAGE AS builder
|
||||
|
||||
USER root
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
WORKDIR /src/jupyterhub
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yq --no-install-recommends \
|
||||
RUN apt update -q \
|
||||
&& apt install -yq --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
locales \
|
||||
@@ -38,66 +38,54 @@ RUN apt-get update \
|
||||
python3-venv \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN python3 -m pip install --upgrade setuptools pip build wheel
|
||||
RUN npm install --global yarn
|
||||
|
||||
&& apt clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade setuptools pip build wheel \
|
||||
&& npm install --global yarn
|
||||
# copy everything except whats in .dockerignore, its a
|
||||
# compromise between needing to rebuild and maintaining
|
||||
# what needs to be part of the build
|
||||
COPY . /src/jupyterhub/
|
||||
WORKDIR /src/jupyterhub
|
||||
|
||||
# Build client component packages (they will be copied into ./share and
|
||||
# packaged with the built wheel.)
|
||||
RUN python3 -m build --wheel
|
||||
RUN python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||
|
||||
COPY . .
|
||||
ARG PIP_CACHE_DIR=/tmp/pip-cache
|
||||
RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
|
||||
python3 -m build --wheel \
|
||||
&& python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
USER root
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yq --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
locales \
|
||||
python3-pip \
|
||||
python3-pycurl \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV SHELL=/bin/bash \
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
SHELL=/bin/bash \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US.UTF-8
|
||||
|
||||
RUN locale-gen $LC_ALL
|
||||
|
||||
# always make sure pip is up to date!
|
||||
RUN python3 -m pip install --no-cache --upgrade setuptools pip
|
||||
|
||||
RUN npm install -g configurable-http-proxy@^4.2.0 \
|
||||
&& rm -rf ~/.npm
|
||||
|
||||
# install the wheels we built in the first stage
|
||||
COPY --from=builder /src/jupyterhub/wheelhouse /tmp/wheelhouse
|
||||
RUN python3 -m pip install --no-cache /tmp/wheelhouse/*
|
||||
|
||||
RUN mkdir -p /srv/jupyterhub/
|
||||
WORKDIR /srv/jupyterhub/
|
||||
LANGUAGE=en_US.UTF-8 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||
LABEL org.jupyter.service="jupyterhub"
|
||||
|
||||
WORKDIR /srv/jupyterhub
|
||||
|
||||
RUN apt update -q \
|
||||
&& apt install -yq --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
locales \
|
||||
python-is-python3 \
|
||||
python3-pip \
|
||||
python3-pycurl \
|
||||
nodejs \
|
||||
npm \
|
||||
&& locale-gen $LC_ALL \
|
||||
&& npm install -g configurable-http-proxy@^4.2.0 \
|
||||
# clean cache and logs
|
||||
&& rm -rf /var/lib/apt/lists/* /var/log/* /var/tmp/* ~/.npm \
|
||||
&& find / -type d -name '__pycache__' -prune -exec rm -rf {} \;
|
||||
# install the wheels we built in the first stage
|
||||
RUN --mount=type=cache,from=builder,source=/src/jupyterhub/wheelhouse,target=/tmp/wheelhouse \
|
||||
# always make sure pip is up to date!
|
||||
python3 -m pip install --no-compile --no-cache-dir --upgrade setuptools pip \
|
||||
&& python3 -m pip install --no-compile --no-cache-dir /tmp/wheelhouse/*
|
||||
|
||||
CMD ["jupyterhub"]
|
||||
|
@@ -12,7 +12,7 @@ print(f"DATA_FILES_PATH={DATA_FILES_PATH}", end=" ")
|
||||
DATA_FILES_PATH = Path(DATA_FILES_PATH)
|
||||
assert DATA_FILES_PATH.is_dir(), DATA_FILES_PATH
|
||||
for subpath in (
|
||||
"templates/page.html",
|
||||
"templates/spawn.html",
|
||||
"static/css/style.min.css",
|
||||
"static/components/jquery/dist/jquery.js",
|
||||
"static/js/admin-react.js",
|
||||
@@ -28,6 +28,7 @@ for subpath in (
|
||||
"alembic.ini",
|
||||
"alembic/versions/833da8570507_rbac.py",
|
||||
"event-schemas/server-actions/v1.yaml",
|
||||
"singleuser/templates/page.html",
|
||||
):
|
||||
path = jupyterhub_path / subpath
|
||||
assert path.is_file(), path
|
||||
|
@@ -6,7 +6,7 @@ info:
|
||||
description: The REST API for JupyterHub
|
||||
license:
|
||||
name: BSD-3-Clause
|
||||
version: 4.0.0b1
|
||||
version: 4.0.0
|
||||
servers:
|
||||
- url: /hub/api
|
||||
security:
|
||||
|
@@ -50,6 +50,7 @@ myst_heading_anchors = 2
|
||||
|
||||
myst_enable_extensions = [
|
||||
# available extensions: https://myst-parser.readthedocs.io/en/latest/syntax/optional.html
|
||||
"attrs_inline",
|
||||
"colon_fence",
|
||||
"deflist",
|
||||
"fieldlist",
|
||||
@@ -185,6 +186,7 @@ linkcheck_ignore = [
|
||||
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
||||
"https://github.com/jupyterhub/jupyterhub/compare/", # too many comparisons in changelog
|
||||
r"https?://(localhost|127.0.0.1).*", # ignore localhost references in auto-links
|
||||
r".*/rest-api.html#.*", # ignore javascript-resolved internal rest-api links
|
||||
r"https://jupyter.chameleoncloud.org", # FIXME: ignore (presumably) short-term SSL issue
|
||||
]
|
||||
linkcheck_anchors_ignore = [
|
||||
@@ -233,8 +235,12 @@ ogp_use_first_image = True
|
||||
# If you are basing changes off another branch/ commit, always change back
|
||||
# rediraffe_branch to main before pushing your changes upstream.
|
||||
#
|
||||
rediraffe_branch = "main"
|
||||
rediraffe_branch = os.environ.get("REDIRAFFE_BRANCH", "main")
|
||||
rediraffe_redirects = "redirects.txt"
|
||||
|
||||
# allow 80% match for autogenerated redirects
|
||||
rediraffe_auto_redirect_perc = 80
|
||||
|
||||
# rediraffe_redirects = {
|
||||
# "old-file": "new-folder/new-file-name",
|
||||
# }
|
||||
|
@@ -82,7 +82,7 @@ Additionally, there is usually _very_ little load on the database itself.
|
||||
By far the most taxing activity on the database is the 'list all users' endpoint, primarily used by the [idle-culling service](https://github.com/jupyterhub/jupyterhub-idle-culler).
|
||||
Database-based optimizations have been added to make even these operations feasible for large numbers of users:
|
||||
|
||||
1. State filtering on [GET /users](jupyterhub-rest-API) with `?state=active`,
|
||||
1. State filtering on [GET /hub/api/users?state=active](../reference/rest-api.html#/default/get_users){.external},
|
||||
which limits the number of results in the query to only the relevant subset (added in JupyterHub 1.3), rather than all users.
|
||||
2. [Pagination](api-pagination) of all list endpoints, allowing the request of a large number of resources to be more fairly balanced with other Hub activities across multiple requests (added in 2.0).
|
||||
|
||||
@@ -95,8 +95,14 @@ The Hub and its database are not involved in most requests to single-user server
|
||||
|
||||
JupyterHub supports a variety of database backends via [SQLAlchemy][].
|
||||
The default is sqlite, which works great for many cases, but you should be able to use many backends supported by SQLAlchemy.
|
||||
Usually, this will mean PostgreSQL or MySQL, both of which are well tested with JupyterHub.
|
||||
Usually, this will mean PostgreSQL or MySQL, both of which are officially supported and well tested with JupyterHub, but others may work as well.
|
||||
See [SQLAlchemy's docs][sqlalchemy-dialect] for how to connect to different database backends.
|
||||
Doing so generally involves:
|
||||
|
||||
1. installing a Python package that provides a client implementation, and
|
||||
2. setting [](JupyterHub.db_url) to connect to your database with the specified implementation
|
||||
|
||||
[sqlalchemy-dialect]: https://docs.sqlalchemy.org/en/20/dialects/
|
||||
[sqlalchemy]: https://www.sqlalchemy.org
|
||||
|
||||
### Default backend: SQLite
|
||||
@@ -109,14 +115,16 @@ For production systems, SQLite has some disadvantages when used with JupyterHub:
|
||||
|
||||
- `upgrade-db` may not always work, and you may need to start with a fresh database
|
||||
- `downgrade-db` **will not** work if you want to rollback to an earlier
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading (JupyterHub automatically creates a date-stamped backup file when upgrading sqlite)
|
||||
|
||||
The sqlite documentation provides a helpful page about [when to use SQLite and
|
||||
where traditional RDBMS may be a better choice](https://sqlite.org/whentouse.html).
|
||||
|
||||
### Picking your database backend (PostgreSQL, MySQL)
|
||||
|
||||
When running a long term deployment or a production system, we recommend using a full-fledged relational database, such as [PostgreSQL](https://www.postgresql.org) or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE` statement.
|
||||
When running a long term deployment or a production system, we recommend using a full-fledged relational database, such as [PostgreSQL](https://www.postgresql.org) or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE` statement, which is used in some database upgrade steps.
|
||||
|
||||
In general, you select your database backend with [](JupyterHub.db_url), and can further configure it (usually not necessary) with [](JupyterHub.db_kwargs).
|
||||
|
||||
## Notes and Tips
|
||||
|
||||
@@ -132,14 +140,25 @@ multiple processes which might try to access the file at the same time.
|
||||
### PostgreSQL
|
||||
|
||||
We recommend using PostgreSQL for production if you are unsure whether to use
|
||||
MySQL or PostgreSQL or if you do not have a strong preference. There is
|
||||
additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
MySQL or PostgreSQL or if you do not have a strong preference.
|
||||
There is additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
|
||||
For example, to connect to a postgres database with psycopg2:
|
||||
|
||||
1. install psycopg2: `pip instal psycopg2` (or `psycopg2-binary` to avoid compilation, which is [not recommended for production][psycopg2-binary])
|
||||
2. set authentication via environment variables `PGUSER` and `PGPASSWORD`
|
||||
3. configure [](JupyterHub.db_url):
|
||||
|
||||
```python
|
||||
c.JupyterHub.db_url = "postgres+psycopg2://my-postgres-server:5432/my-db-name"
|
||||
```
|
||||
|
||||
[psycopg2-binary]: https://www.psycopg.org/docs/install.html#psycopg-vs-psycopg-binary
|
||||
|
||||
### MySQL / MariaDB
|
||||
|
||||
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
||||
isn't available for py3).
|
||||
- You also need to set `pool_recycle` to some value (typically 60 - 300)
|
||||
- You should probably use the `pymysql` or `mysqlclient` sqlalchemy provider, or another backend [recommended by sqlalchemy](https://docs.sqlalchemy.org/en/20/dialects/mysql.html#dialect-mysql)
|
||||
- You also need to set `pool_recycle` to some value (typically 60 - 300, JupyterHub will default to 60)
|
||||
which depends on your MySQL setup. This is necessary since MySQL kills
|
||||
connections serverside if they've been idle for a while, and the connection
|
||||
from the hub will be idle for longer than most connections. This behavior
|
||||
@@ -153,3 +172,12 @@ additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
correctly. Later versions of MariaDB and MySQL should set these values by
|
||||
default, as well as have a default `DYNAMIC` `row_format` and pose no trouble
|
||||
to users.
|
||||
|
||||
For example, to connect to a mysql database with mysqlclient:
|
||||
|
||||
1. install mysqlclient: `pip install mysqlclient`
|
||||
2. configure [](JupyterHub.db_url):
|
||||
|
||||
```python
|
||||
c.JupyterHub.db_url = "mysql+mysqldb://myuser:mypassword@my-sql-server:3306/my-db-name"
|
||||
```
|
||||
|
@@ -1,23 +1,45 @@
|
||||
# This file contains rediraffe redirects as generated from the docs/source/conf.py file
|
||||
# For more information, see rediraffe configuration in the conf.py file.
|
||||
|
||||
|
||||
"changelog.md" "reference/changelog.md"
|
||||
"contributor-list.md" "contributing/contributor-list.md"
|
||||
"gallery-jhub-deployments.md" "reference/gallery-jhub-deployments.md"
|
||||
"installation-basics.md" "tutorial/installation-basics.md"
|
||||
"quickstart.md" "tutorial/quickstart.md"
|
||||
"quickstart-docker.md" "tutorial/quickstart-docker.md"
|
||||
"troubleshooting.md" "faq/troubleshooting.md"
|
||||
|
||||
"admin/capacity-planning.md" "explanation/capacity-planning.md"
|
||||
"admin/log-messages.md" "howto/log-messages.md"
|
||||
"admin/upgrading.md" "howto/upgrading.md"
|
||||
|
||||
"events/index.md" "reference/event-logging.md"
|
||||
|
||||
"getting-started/authenticators-users-basics.md" "tutorial/getting-started/authenticators-users-basics.md"
|
||||
"getting-started/config-basics.md" "tutorial/getting-started/config-basics.md"
|
||||
"getting-started/faq.md" "faq/faq.md"
|
||||
"getting-started/institutional-faq.md" "faq/institutional-faq.md"
|
||||
"getting-started/networking-basics.md" "tutorial/getting-started/networking-basics.md"
|
||||
"getting-started/services-basics.md" "tutorial/getting-started/services-basics.md"
|
||||
"getting-started/spawners-basics.md" "tutorial/getting-started/spawners-basics.md"
|
||||
|
||||
"reference/api-only.md" "howto/api-only.md"
|
||||
"reference/config-ghoauth.md" "howto/configuration/config-ghoauth.md"
|
||||
"reference/config-proxy.md" "howto/configuration/config-proxy.md"
|
||||
"admin/log-messages.md" "howto/log-messages.md"
|
||||
"reference/database.md" "explanation/database.md"
|
||||
"reference/oauth.md" "explanation/oauth.md"
|
||||
"reference/proxy.md" "howto/proxy.md"
|
||||
"reference/templates.md" "howto/templates.md"
|
||||
"quickstart-docker.md" "tutorial/quickstart-docker.md"
|
||||
"reference/config-examples.md" "howto/index.md"
|
||||
"getting-started/institutional-faq.md" "faq/institutional-faq.md"
|
||||
"troubleshooting.md" "faq/troubleshooting.md"
|
||||
"reference/config-sudo.md" "howto/configuration/config-sudo.md"
|
||||
"reference/config-user-env.md" "howto/configuration/config-user-env.md"
|
||||
"reference/rest.md" "howto/rest.md"
|
||||
"reference/separate-proxy.md" "howto/separate-proxy.md"
|
||||
"admin/upgrading.md" "howto/upgrading.md"
|
||||
"installation-basics.md" "tutorial/installation-basics.md"
|
||||
"quickstart.md" "tutorial/quickstart.md"
|
||||
"events/index.md" "reference/event-logging.md"
|
||||
"reference/server-api.md" "tutorial/server-api.md"
|
||||
"reference/websecurity.md" "explanation/websecurity.md"
|
||||
|
||||
# -- JupyterHub 4.0 --
|
||||
# redirects above are up-to-date as of JupyterHub 4.0
|
||||
# add future redirects below
|
||||
# (e.g. with `make rediraffewritediff`)
|
||||
|
@@ -273,7 +273,7 @@ c.Spawner.auth_state_hook = auth_state_hook
|
||||
:::
|
||||
|
||||
Some identity providers may have their own concept of group membership that you would like to preserve in JupyterHub.
|
||||
This is now possible with `Authenticator.managed_groups`.
|
||||
This is now possible with `Authenticator.manage_groups`.
|
||||
|
||||
You can set the config:
|
||||
|
||||
|
@@ -8,7 +8,9 @@ command line for details.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 4.0 (beta) - 2023-XX-YY
|
||||
## 4.0
|
||||
|
||||
### 4.0.0 - 2023-04-20
|
||||
|
||||
4.0 is a major release, but a small one.
|
||||
There are three major changes that _should_ be invisible to most users:
|
||||
@@ -24,7 +26,7 @@ There are three major changes that _should_ be invisible to most users:
|
||||
|
||||
In addition to these, thanks to contributions from this years Outreachy interns, we have reorganized the documentation according to [diataxis](https://diataxis.fr), improved accessibility of JupyterHub pages, and improved testing.
|
||||
|
||||
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/3.1.0...HEAD))
|
||||
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/3.1.0...4.0.0))
|
||||
|
||||
#### API and Breaking Changes
|
||||
|
||||
@@ -33,18 +35,23 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### New features added
|
||||
|
||||
- add Spawner.server_token_scopes config [#4400](https://github.com/jupyterhub/jupyterhub/pull/4400) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Make singleuser server-extension default [#4354](https://github.com/jupyterhub/jupyterhub/pull/4354) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- singleuser auth as server extension [#3888](https://github.com/jupyterhub/jupyterhub/pull/3888) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Dynamic table for changing customizable properties of groups [#3651](https://github.com/jupyterhub/jupyterhub/pull/3651) ([@vladfreeze](https://github.com/vladfreeze), [@minrk](https://github.com/minrk), [@naatebarber](https://github.com/naatebarber), [@manics](https://github.com/manics))
|
||||
|
||||
#### Enhancements made
|
||||
|
||||
- admin page: improve display of long lists (groups, etc.) [#4417](https://github.com/jupyterhub/jupyterhub/pull/4417) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk), [@ryanlovett](https://github.com/ryanlovett))
|
||||
- add a few more buckets for server_spawn_duration_seconds [#4352](https://github.com/jupyterhub/jupyterhub/pull/4352) ([@shaneknapp](https://github.com/shaneknapp), [@yuvipanda](https://github.com/yuvipanda))
|
||||
- Standardize styling on input fields by moving common form CSS to page.less [#4294](https://github.com/jupyterhub/jupyterhub/pull/4294) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Improve contrast on muted text [#4326](https://github.com/jupyterhub/jupyterhub/pull/4326) ([@bl-aire](https://github.com/bl-aire), [@minrk](https://github.com/minrk))
|
||||
- Standardize styling on input fields by moving common form CSS to page.less [#4294](https://github.com/jupyterhub/jupyterhub/pull/4294) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Bugs fixed
|
||||
|
||||
- make sure named server URLs include trailing slash [#4402](https://github.com/jupyterhub/jupyterhub/pull/4402) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- fix inclusion of singleuser/templates/page.html in wheel [#4387](https://github.com/jupyterhub/jupyterhub/pull/4387) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- exponential_backoff: preserve jitter when max_wait is reached [#4383](https://github.com/jupyterhub/jupyterhub/pull/4383) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- admin panel: fix condition for start/stop buttons on user servers [#4365](https://github.com/jupyterhub/jupyterhub/pull/4365) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- avoid logging error when browsers send invalid cookies [#4356](https://github.com/jupyterhub/jupyterhub/pull/4356) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio))
|
||||
- test and fix deprecated load_groups list [#4299](https://github.com/jupyterhub/jupyterhub/pull/4299) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
@@ -55,6 +62,16 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### Maintenance and upkeep improvements
|
||||
|
||||
- add remaining redirects for docs reorg [#4423](https://github.com/jupyterhub/jupyterhub/pull/4423) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Disable dev traitlets [#4419](https://github.com/jupyterhub/jupyterhub/pull/4419) ([@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: rename to .yaml [#4409](https://github.com/jupyterhub/jupyterhub/pull/4409) ([@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: fix syntax error of not using quotes for ##:## [#4408](https://github.com/jupyterhub/jupyterhub/pull/4408) ([@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: monthly updates of github actions [#4403](https://github.com/jupyterhub/jupyterhub/pull/4403) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- Refresh 4.0 changelog [#4396](https://github.com/jupyterhub/jupyterhub/pull/4396) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Reduce size of jupyterhub image [#4394](https://github.com/jupyterhub/jupyterhub/pull/4394) ([@alekseyolg](https://github.com/alekseyolg), [@minrk](https://github.com/minrk))
|
||||
- Selenium: updating test_oauth_page [#4393](https://github.com/jupyterhub/jupyterhub/pull/4393) ([@mouse1203](https://github.com/mouse1203), [@minrk](https://github.com/minrk))
|
||||
- avoid warning on engine_connect listener [#4392](https://github.com/jupyterhub/jupyterhub/pull/4392) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@manics](https://github.com/manics))
|
||||
- remove pin from singleuser [#4379](https://github.com/jupyterhub/jupyterhub/pull/4379) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@mathbunnyru](https://github.com/mathbunnyru))
|
||||
- temporary fix: pin base-notebook tag [#4348](https://github.com/jupyterhub/jupyterhub/pull/4348) ([@minrk](https://github.com/minrk))
|
||||
- simplify some async fixtures [#4332](https://github.com/jupyterhub/jupyterhub/pull/4332) ([@minrk](https://github.com/minrk), [@GeorgianaElena](https://github.com/GeorgianaElena), [@Sheila-nk](https://github.com/Sheila-nk))
|
||||
- Selenium: adding new cases that covered Admin UI page [#4328](https://github.com/jupyterhub/jupyterhub/pull/4328) ([@mouse1203](https://github.com/mouse1203), [@minrk](https://github.com/minrk))
|
||||
@@ -76,9 +93,19 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### Documentation improvements
|
||||
|
||||
- Fix variable spelling. [#4398](https://github.com/jupyterhub/jupyterhub/pull/4398) ([@ryanlovett](https://github.com/ryanlovett), [@manics](https://github.com/manics))
|
||||
- Remove bracket around link text without address [#4416](https://github.com/jupyterhub/jupyterhub/pull/4416) ([@crazytan](https://github.com/crazytan), [@minrk](https://github.com/minrk))
|
||||
- add some more detail and examples to database doc [#4399](https://github.com/jupyterhub/jupyterhub/pull/4399) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Add emphasis about role loading and hub restarts. [#4390](https://github.com/jupyterhub/jupyterhub/pull/4390) ([@ryanlovett](https://github.com/ryanlovett), [@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Re-enable links to REST API [#4386](https://github.com/jupyterhub/jupyterhub/pull/4386) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- reduce nested hierarchy in docs organization [#4377](https://github.com/jupyterhub/jupyterhub/pull/4377) ([@alwasega](https://github.com/alwasega), [@minrk](https://github.com/minrk))
|
||||
- changelog for 4.0 beta [#4375](https://github.com/jupyterhub/jupyterhub/pull/4375) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Getting started link broken [#4374](https://github.com/jupyterhub/jupyterhub/pull/4374) ([@3coins](https://github.com/3coins), [@minrk](https://github.com/minrk))
|
||||
- add collaboration accounts tutorial [#4373](https://github.com/jupyterhub/jupyterhub/pull/4373) ([@minrk](https://github.com/minrk), [@fperez](https://github.com/fperez), [@ryanlovett](https://github.com/ryanlovett))
|
||||
- Updated the top-level index file [#4368](https://github.com/jupyterhub/jupyterhub/pull/4368) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91))
|
||||
- JupyterHub sphinx theme [#4363](https://github.com/jupyterhub/jupyterhub/pull/4363) ([@minrk](https://github.com/minrk), [@choldgraf](https://github.com/choldgraf))
|
||||
- Remove PDF links from README.md [#4358](https://github.com/jupyterhub/jupyterhub/pull/4358) ([@pnasrat](https://github.com/pnasrat), [@manics](https://github.com/manics))
|
||||
- add singleuser explanation doc [#4357](https://github.com/jupyterhub/jupyterhub/pull/4357) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Updates to the documentation Contribution section [#4355](https://github.com/jupyterhub/jupyterhub/pull/4355) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91), [@minrk](https://github.com/minrk))
|
||||
- Restructured references section of the docs [#4343](https://github.com/jupyterhub/jupyterhub/pull/4343) ([@alwasega](https://github.com/alwasega), [@minrk](https://github.com/minrk), [@sgibson91](https://github.com/sgibson91))
|
||||
- Document use of pytest-asyncio in JupyterHub test suite [#4341](https://github.com/jupyterhub/jupyterhub/pull/4341) ([@Sheila-nk](https://github.com/Sheila-nk), [@minrk](https://github.com/minrk), [@alwasega](https://github.com/alwasega))
|
||||
@@ -107,9 +134,9 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
||||
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
||||
|
||||
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2022-12-05&to=2023-02-27&type=c))
|
||||
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2022-12-05&to=2023-04-20&type=c))
|
||||
|
||||
@3coins ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A3coins+updated%3A2022-12-05..2023-02-27&type=Issues)) | @ajcollett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajcollett+updated%3A2022-12-05..2023-02-27&type=Issues)) | @ajpower ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajpower+updated%3A2022-12-05..2023-02-27&type=Issues)) | @alwasega ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalwasega+updated%3A2022-12-05..2023-02-27&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2022-12-05..2023-02-27&type=Issues)) | @bl-aire ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abl-aire+updated%3A2022-12-05..2023-02-27&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2022-12-05..2023-02-27&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2022-12-05..2023-02-27&type=Issues)) | @dependabot ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adependabot+updated%3A2022-12-05..2023-02-27&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2022-12-05..2023-02-27&type=Issues)) | @julietKiloRomeo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AjulietKiloRomeo+updated%3A2022-12-05..2023-02-27&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2022-12-05..2023-02-27&type=Issues)) | @meeseeksdev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksdev+updated%3A2022-12-05..2023-02-27&type=Issues)) | @meeseeksmachine ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2022-12-05..2023-02-27&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @mouse1203 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amouse1203+updated%3A2022-12-05..2023-02-27&type=Issues)) | @naatebarber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Anaatebarber+updated%3A2022-12-05..2023-02-27&type=Issues)) | @pnasrat ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apnasrat+updated%3A2022-12-05..2023-02-27&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apre-commit-ci+updated%3A2022-12-05..2023-02-27&type=Issues)) | @sgibson91 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgibson91+updated%3A2022-12-05..2023-02-27&type=Issues)) | @shaneknapp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashaneknapp+updated%3A2022-12-05..2023-02-27&type=Issues)) | @Sheila-nk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASheila-nk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @stevejpurves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astevejpurves+updated%3A2022-12-05..2023-02-27&type=Issues)) | @TaofeeqatDev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATaofeeqatDev+updated%3A2022-12-05..2023-02-27&type=Issues)) | @vladfreeze ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avladfreeze+updated%3A2022-12-05..2023-02-27&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2022-12-05..2023-02-27&type=Issues))
|
||||
@3coins ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A3coins+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ajcollett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajcollett+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ajpower ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajpower+updated%3A2022-12-05..2023-04-20&type=Issues)) | @alekseyolg ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalekseyolg+updated%3A2022-12-05..2023-04-20&type=Issues)) | @alwasega ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalwasega+updated%3A2022-12-05..2023-04-20&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2022-12-05..2023-04-20&type=Issues)) | @bl-aire ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abl-aire+updated%3A2022-12-05..2023-04-20&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2022-12-05..2023-04-20&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2022-12-05..2023-04-20&type=Issues)) | @crazytan ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acrazytan+updated%3A2022-12-05..2023-04-20&type=Issues)) | @dependabot ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adependabot+updated%3A2022-12-05..2023-04-20&type=Issues)) | @fperez ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Afperez+updated%3A2022-12-05..2023-04-20&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2022-12-05..2023-04-20&type=Issues)) | @julietKiloRomeo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AjulietKiloRomeo+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2022-12-05..2023-04-20&type=Issues)) | @mathbunnyru ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amathbunnyru+updated%3A2022-12-05..2023-04-20&type=Issues)) | @meeseeksdev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksdev+updated%3A2022-12-05..2023-04-20&type=Issues)) | @meeseeksmachine ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2022-12-05..2023-04-20&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @mouse1203 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amouse1203+updated%3A2022-12-05..2023-04-20&type=Issues)) | @naatebarber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Anaatebarber+updated%3A2022-12-05..2023-04-20&type=Issues)) | @pnasrat ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apnasrat+updated%3A2022-12-05..2023-04-20&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apre-commit-ci+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2022-12-05..2023-04-20&type=Issues)) | @sgibson91 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgibson91+updated%3A2022-12-05..2023-04-20&type=Issues)) | @shaneknapp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashaneknapp+updated%3A2022-12-05..2023-04-20&type=Issues)) | @Sheila-nk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASheila-nk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @stevejpurves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astevejpurves+updated%3A2022-12-05..2023-04-20&type=Issues)) | @TaofeeqatDev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATaofeeqatDev+updated%3A2022-12-05..2023-04-20&type=Issues)) | @vladfreeze ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avladfreeze+updated%3A2022-12-05..2023-04-20&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2022-12-05..2023-04-20&type=Issues))
|
||||
|
||||
## 3.1
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Event logging and telemetry
|
||||
|
||||
JupyterHub can be configured to record structured events from a running server using Jupyter's [Telemetry System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this [page].
|
||||
JupyterHub can be configured to record structured events from a running server using Jupyter's [Telemetry System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this page.
|
||||
|
||||
## How to emit events
|
||||
|
||||
|
@@ -83,7 +83,6 @@ easy to do with RStudio too.
|
||||
- Slurm job dispatched on Crestone compute cluster
|
||||
- log troubleshooting
|
||||
- Profiles in IPython Clusters tab
|
||||
- [Parallel Processing with JupyterHub tutorial](https://curc.readthedocs.io/en/latest/gateways/parallel-programming-jupyter.html)
|
||||
|
||||
### George Washington University
|
||||
|
||||
|
@@ -124,6 +124,8 @@ for project_name, project in project_config["projects"].items():
|
||||
|
||||
The `members` step could be skipped if group membership is managed by the authenticator, or handled via the admin UI later, in which case we only need to handle group _creation_ and role assignment.
|
||||
|
||||
This configuration code runs when jupyterhub starts up, and as noted above, users and groups cannot have their role assignments change without restarting JupyterHub. If new collaboration groups are created (within configuration, via the admin page, or via the Authenticator), the hub will need to be restarted in order for it to load roles for those new groups.
|
||||
|
||||
### Distinguishing collaborative servers
|
||||
|
||||
Finally, we want to enable RTC only on the collaborative user servers (and _only_ the collaborative user servers),
|
||||
|
@@ -37,7 +37,6 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-icons": "^4.1.0",
|
||||
"react-multi-select-component": "^3.0.7",
|
||||
"react-object-table-viewer": "^1.0.7",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
@@ -68,7 +67,7 @@
|
||||
"prettier": "^2.2.1",
|
||||
"sinon": "^13.0.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"webpack": "^5.6.0",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.3"
|
||||
}
|
||||
|
21
jsx/src/components/ReactObjectTableViewer/LICENSE
Normal file
21
jsx/src/components/ReactObjectTableViewer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 jinkwon.lee<uzmystic@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@@ -0,0 +1,65 @@
|
||||
// Originally copied from
|
||||
// https://github.com/jinkwon/react-object-table-viewer/blob/f29827028fad547a0a17e044567cf1486849fb7a/src/ReactObjectTableViewer.tsx
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ReactObjectTableViewer = (props) => {
|
||||
const opt = props;
|
||||
|
||||
const data = opt.data;
|
||||
const keys = Object.keys(data || {}) || [];
|
||||
|
||||
return (
|
||||
<table
|
||||
className={opt.className}
|
||||
style={{
|
||||
...opt.style,
|
||||
}}
|
||||
>
|
||||
<tbody>
|
||||
{keys.map((k, key) => {
|
||||
const val = data[k];
|
||||
const isObject = typeof val === "object";
|
||||
const isElement = React.isValidElement(val);
|
||||
|
||||
return (
|
||||
<tr key={key}>
|
||||
<th
|
||||
style={{
|
||||
...opt.keyStyle,
|
||||
}}
|
||||
>
|
||||
{k}
|
||||
</th>
|
||||
{isObject && (
|
||||
<td>
|
||||
{isElement && val}
|
||||
{!isElement && <ReactObjectTableViewer {...opt} data={val} />}
|
||||
</td>
|
||||
)}
|
||||
{!isObject && (
|
||||
<td
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
...opt.valueStyle,
|
||||
}}
|
||||
>{`${val}`}</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
ReactObjectTableViewer.propTypes = {
|
||||
data: PropTypes.object,
|
||||
style: PropTypes.objectOf(PropTypes.string),
|
||||
keyStyle: PropTypes.objectOf(PropTypes.string),
|
||||
valueStyle: PropTypes.objectOf(PropTypes.string),
|
||||
className: PropTypes.string,
|
||||
layout: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ReactObjectTableViewer;
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { debounce } from "lodash";
|
||||
import PropTypes from "prop-types";
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CardGroup,
|
||||
Collapse,
|
||||
} from "react-bootstrap";
|
||||
import ReactObjectTableViewer from "react-object-table-viewer";
|
||||
import ReactObjectTableViewer from "../ReactObjectTableViewer/ReactObjectTableViewer";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
||||
@@ -29,6 +29,13 @@ const AccessServerButton = ({ url }) => (
|
||||
</a>
|
||||
);
|
||||
|
||||
const RowListItem = ({ text }) => (
|
||||
<span className="server-dashboard-row-list-item">{text}</span>
|
||||
);
|
||||
RowListItem.propTypes = {
|
||||
text: PropTypes.string,
|
||||
};
|
||||
|
||||
const ServerDashboard = (props) => {
|
||||
let base_url = window.base_url || "/";
|
||||
// sort methods
|
||||
@@ -236,8 +243,13 @@ const ServerDashboard = (props) => {
|
||||
break;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
// cast arrays (e.g. roles, groups) to string
|
||||
value = value.sort().join(", ");
|
||||
value = (
|
||||
<Fragment>
|
||||
{value.sort().flatMap((v) => (
|
||||
<RowListItem text={v} />
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
result[key] = value;
|
||||
return result;
|
||||
|
@@ -30,3 +30,11 @@
|
||||
tr.noborder > td {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.server-dashboard-row-list-item {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
margin: 2px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
@@ -5963,7 +5963,7 @@ react-bootstrap@^2.1.1:
|
||||
uncontrollable "^7.2.1"
|
||||
warning "^4.0.3"
|
||||
|
||||
react-dom@17.0.2, react-dom@^17.0.1:
|
||||
react-dom@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||
@@ -6004,14 +6004,6 @@ react-multi-select-component@^3.0.7:
|
||||
dependencies:
|
||||
goober "^2.0.30"
|
||||
|
||||
react-object-table-viewer@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-object-table-viewer/-/react-object-table-viewer-1.0.7.tgz#31816021fa4526641c6b66bd9433ec9b78c2e472"
|
||||
integrity sha512-OezCet8+BmEdJJHO5WGPFPRWXxw4Ls6HsV4Uh1kRPlmRXLOTNqWt/ZHmH8NhTl1BA9HkdhEegKVqc2b61wDMLg==
|
||||
dependencies:
|
||||
react "^17.0.2"
|
||||
react-dom "17.0.2"
|
||||
|
||||
react-redux@^7.2.2:
|
||||
version "7.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
|
||||
@@ -6081,7 +6073,7 @@ react-transition-group@^4.4.2:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@^17.0.1, react@^17.0.2:
|
||||
react@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||
@@ -7423,10 +7415,10 @@ webpack-sources@^3.2.3:
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
|
||||
webpack@^5.6.0:
|
||||
version "5.74.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
|
||||
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
|
||||
webpack@^5.76.0:
|
||||
version "5.76.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
|
||||
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
|
||||
dependencies:
|
||||
"@types/eslint-scope" "^3.7.3"
|
||||
"@types/estree" "^0.0.51"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
# version_info updated by running `tbump`
|
||||
version_info = (4, 0, 0, "b1", "")
|
||||
version_info = (4, 0, 0, "", "")
|
||||
|
||||
# pep 440 version: no dot before beta/rc, but before .dev
|
||||
# 0.1.0rc1
|
||||
|
@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
import alembic.command
|
||||
import alembic.config
|
||||
import sqlalchemy
|
||||
from alembic.script import ScriptDirectory
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
@@ -902,10 +903,12 @@ def register_ping_connection(engine):
|
||||
https://docs.sqlalchemy.org/en/rel_1_1/core/pooling.html#disconnect-handling-pessimistic
|
||||
"""
|
||||
|
||||
@event.listens_for(engine, "engine_connect")
|
||||
def ping_connection(connection, branch=None):
|
||||
# TODO: remove unused branch arg when we require sqlalchemy 2.0
|
||||
|
||||
# listeners are normally registered as a decorator,
|
||||
# but we need two different signatures to avoid SAWarning:
|
||||
# The argument signature for the "ConnectionEvents.engine_connect" event listener has changed
|
||||
# while we support sqla 1.4 and 2.0.
|
||||
# @event.listens_for(engine, "engine_connect")
|
||||
def ping_connection(connection):
|
||||
# turn off "close with result". This flag is only used with
|
||||
# "connectionless" execution, otherwise will be False in any case
|
||||
save_should_close_with_result = connection.should_close_with_result
|
||||
@@ -939,6 +942,17 @@ def register_ping_connection(engine):
|
||||
# restore "close with result"
|
||||
connection.should_close_with_result = save_should_close_with_result
|
||||
|
||||
# sqla v1/v2 compatible invocation of @event.listens_for:
|
||||
def ping_connection_v1(connection, branch=None):
|
||||
"""sqlalchemy < 2.0 compatibility"""
|
||||
return ping_connection(connection)
|
||||
|
||||
if int(sqlalchemy.__version__.split(".", 1)[0]) >= 2:
|
||||
listener = ping_connection
|
||||
else:
|
||||
listener = ping_connection_v1
|
||||
event.listens_for(engine, "engine_connect")(listener)
|
||||
|
||||
|
||||
def check_db_revision(engine):
|
||||
"""Check the JupyterHub database revision
|
||||
|
@@ -52,7 +52,7 @@ def get_default_roles():
|
||||
'description': 'Post activity only',
|
||||
'scopes': [
|
||||
'users:activity!user',
|
||||
'access:servers!user',
|
||||
'access:servers!server',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -382,6 +382,23 @@ class Spawner(LoggingConfigurable):
|
||||
scopes.append(f"access:servers!server={self.user.name}/{self.name}")
|
||||
return sorted(set(scopes))
|
||||
|
||||
server_token_scopes = Union(
|
||||
[List(Unicode()), Callable()],
|
||||
help="""The list of scopes to request for $JUPYTERHUB_API_TOKEN
|
||||
|
||||
If not specified, the scopes in the `server` role will be used
|
||||
(unchanged from pre-4.0).
|
||||
|
||||
If callable, will be called with the Spawner instance as its sole argument
|
||||
(JupyterHub user available as spawner.user).
|
||||
|
||||
JUPYTERHUB_API_TOKEN will be assigned the _subset_ of these scopes
|
||||
that are held by the user (as in oauth_client_allowed_scopes).
|
||||
|
||||
.. versionadded:: 4.0
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
will_resume = Bool(
|
||||
False,
|
||||
help="""Whether the Spawner will resume on next start
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
from functools import partial
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import pytest
|
||||
from selenium.common.exceptions import (
|
||||
@@ -921,8 +922,15 @@ async def test_oauth_page(
|
||||
service_url = url_path_join(public_url(app, service) + 'owhoami/?arg=x')
|
||||
await in_thread(browser.get, (service_url))
|
||||
expected_client_id = service.name
|
||||
expected_redirect_url = app.base_url + f"servises/{service.name}/oauth_callback"
|
||||
assert expected_client_id, expected_redirect_url in browser.current_url
|
||||
expected_redirect_url = url_path_join(
|
||||
app.base_url + f"services/{service.name}/oauth_callback"
|
||||
)
|
||||
# decode the URL
|
||||
query_params = parse_qs(urlparse(browser.current_url).query)
|
||||
query_params = parse_qs(urlparse(query_params['next'][0]).query)
|
||||
|
||||
assert f"service-{expected_client_id}" == query_params['client_id'][0]
|
||||
assert expected_redirect_url == query_params['redirect_uri'][0]
|
||||
|
||||
# login user
|
||||
await login(browser, user.name, pass_w=str(user.name))
|
||||
@@ -1092,7 +1100,6 @@ async def test_start_stop_all_servers_on_admin_page(app, browser, admin_user):
|
||||
btns = {
|
||||
class_name: get_users_buttons(browser, class_name) for class_name in class_names
|
||||
}
|
||||
print(btns)
|
||||
assert (
|
||||
len(btns["start-button"])
|
||||
== len(btns["secondary"])
|
||||
|
@@ -35,7 +35,7 @@ def generate_old_db(env_dir, hub_version, db_url):
|
||||
pkgs.append('sqlalchemy<2')
|
||||
|
||||
if 'mysql' in db_url:
|
||||
pkgs.append('mysql-connector-python')
|
||||
pkgs.append('mysqlclient')
|
||||
elif 'postgres' in db_url:
|
||||
pkgs.append('psycopg2-binary')
|
||||
check_call([env_pip, 'install'] + pkgs)
|
||||
|
@@ -848,8 +848,12 @@ async def test_server_token_role(app):
|
||||
orm_server_token = orm.APIToken.find(app.db, server_token)
|
||||
assert orm_server_token
|
||||
|
||||
server_role = orm.Role.find(app.db, 'server')
|
||||
assert set(server_role.scopes) == set(orm_server_token.scopes)
|
||||
# resolve `!server` filter in server role
|
||||
server_role_scopes = {
|
||||
s.replace("!server", f"!server={user.name}/")
|
||||
for s in orm.Role.find(app.db, "server").scopes
|
||||
}
|
||||
assert set(orm_server_token.scopes) == server_role_scopes
|
||||
|
||||
assert orm_server_token.user.name == user.name
|
||||
assert user.api_tokens == [orm_server_token]
|
||||
|
@@ -20,7 +20,7 @@ from ..objects import Hub, Server
|
||||
from ..scopes import access_scopes
|
||||
from ..spawner import LocalProcessSpawner, Spawner
|
||||
from ..user import User
|
||||
from ..utils import AnyTimeoutError, new_token, url_path_join
|
||||
from ..utils import AnyTimeoutError, maybe_future, new_token, url_path_join
|
||||
from .mocking import public_url
|
||||
from .test_api import add_user
|
||||
from .utils import async_requests
|
||||
@@ -336,6 +336,12 @@ async def test_spawner_insert_api_token(app):
|
||||
assert found
|
||||
assert found.user.name == user.name
|
||||
assert user.api_tokens == [found]
|
||||
# resolve `!server` filter in server role
|
||||
server_role_scopes = {
|
||||
s.replace("!server", f"!server={user.name}/")
|
||||
for s in orm.Role.find(app.db, "server").scopes
|
||||
}
|
||||
assert set(found.scopes) == server_role_scopes
|
||||
await user.stop()
|
||||
|
||||
|
||||
@@ -361,6 +367,58 @@ async def test_spawner_bad_api_token(app):
|
||||
assert other_user.api_tokens == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"have_scopes, request_scopes, expected_scopes",
|
||||
[
|
||||
(["self"], ["inherit"], ["inherit"]),
|
||||
(["self"], [], ["access:servers!server=USER/", "users:activity!user"]),
|
||||
(
|
||||
["self"],
|
||||
["admin:groups", "read:servers!server"],
|
||||
["users:activity!user", "read:servers!server=USER/"],
|
||||
),
|
||||
(
|
||||
["self", "read:groups!group=x", "users:activity"],
|
||||
["admin:groups", "users:activity"],
|
||||
["read:groups!group=x", "read:groups:name!group=x", "users:activity"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_server_token_scopes(
|
||||
app, username, create_user_with_scopes, have_scopes, request_scopes, expected_scopes
|
||||
):
|
||||
"""Token provided by spawner is not in the db
|
||||
|
||||
Insert token into db as a user-provided token.
|
||||
"""
|
||||
db = app.db
|
||||
|
||||
# apply templating
|
||||
def _format_scopes(scopes):
|
||||
if callable(scopes):
|
||||
|
||||
async def get_scopes(*args):
|
||||
return _format_scopes(await maybe_future(scopes(*args)))
|
||||
|
||||
return get_scopes
|
||||
|
||||
return [s.replace("USER", username) for s in scopes]
|
||||
|
||||
have_scopes = _format_scopes(have_scopes)
|
||||
request_scopes = _format_scopes(request_scopes)
|
||||
expected_scopes = _format_scopes(expected_scopes)
|
||||
|
||||
user = create_user_with_scopes(*have_scopes, name=username)
|
||||
spawner = user.spawner
|
||||
spawner.server_token_scopes = request_scopes
|
||||
|
||||
await user.spawn()
|
||||
orm_token = orm.APIToken.find(db, spawner.api_token)
|
||||
assert orm_token
|
||||
assert set(orm_token.scopes) == set(expected_scopes)
|
||||
await user.stop()
|
||||
|
||||
|
||||
async def test_spawner_delete_server(app):
|
||||
"""Test deleting spawner.server
|
||||
|
||||
|
@@ -53,3 +53,16 @@ def test_sync_groups(app, user, group_names):
|
||||
assert user.orm_user in group.users
|
||||
else:
|
||||
assert user.orm_user not in group.users
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"server_name, path",
|
||||
[
|
||||
("", ""),
|
||||
("name", "name/"),
|
||||
("næme", "n%C3%A6me/"),
|
||||
],
|
||||
)
|
||||
def test_server_url(app, user, server_name, path):
|
||||
user_url = user.url
|
||||
assert user.server_url(server_name) == user_url + path
|
||||
|
@@ -13,7 +13,7 @@ from tornado import gen, web
|
||||
from tornado.httputil import urlencode
|
||||
from tornado.log import app_log
|
||||
|
||||
from . import orm
|
||||
from . import orm, roles, scopes
|
||||
from ._version import __version__, _check_version
|
||||
from .crypto import CryptKeeper, EncryptionUnavailable, InvalidToken, decrypt, encrypt
|
||||
from .metrics import RUNNING_SERVERS, TOTAL_USERS
|
||||
@@ -588,7 +588,7 @@ class User:
|
||||
if not server_name:
|
||||
return self.url
|
||||
else:
|
||||
return url_path_join(self.url, url_escape_path(server_name))
|
||||
return url_path_join(self.url, url_escape_path(server_name), "/")
|
||||
|
||||
def progress_url(self, server_name=''):
|
||||
"""API URL for progress endpoint for a server with a given name"""
|
||||
@@ -673,13 +673,63 @@ class User:
|
||||
orm_server = orm.Server(base_url=base_url)
|
||||
db.add(orm_server)
|
||||
note = "Server at %s" % base_url
|
||||
api_token = self.new_api_token(note=note, roles=['server'])
|
||||
db.commit()
|
||||
|
||||
spawner = self.get_spawner(server_name, replace_failed=True)
|
||||
spawner.server = server = Server(orm_server=orm_server)
|
||||
assert spawner.orm_spawner.server is orm_server
|
||||
|
||||
requested_scopes = spawner.server_token_scopes
|
||||
if callable(requested_scopes):
|
||||
requested_scopes = await maybe_future(requested_scopes(spawner))
|
||||
if not requested_scopes:
|
||||
# nothing requested, default to 'server' role
|
||||
requested_scopes = orm.Role.find(db, "server").scopes
|
||||
requested_scopes = set(requested_scopes)
|
||||
# resolve !server filter, which won't resolve elsewhere,
|
||||
# because this token is not owned by the server's own oauth client
|
||||
server_filter = f"={self.name}/{server_name}"
|
||||
requested_scopes = {
|
||||
scope + server_filter if scope.endswith("!server") else scope
|
||||
for scope in requested_scopes
|
||||
}
|
||||
# ensure activity scope is requested, since activity doesn't work without
|
||||
activity_scope = "users:activity!user"
|
||||
if not {activity_scope, "users:activity", "inherit"}.intersection(
|
||||
requested_scopes
|
||||
):
|
||||
self.log.warning(
|
||||
f"Adding required scope {activity_scope} to server token, missing from Spawner.server_token_scopes. Please make sure to add it!"
|
||||
)
|
||||
requested_scopes |= {activity_scope}
|
||||
|
||||
have_scopes = roles.roles_to_scopes(roles.get_roles_for(self.orm_user))
|
||||
have_scopes |= {"inherit"}
|
||||
jupyterhub_client = (
|
||||
db.query(orm.OAuthClient)
|
||||
.filter_by(
|
||||
identifier="jupyterhub",
|
||||
)
|
||||
.one()
|
||||
)
|
||||
|
||||
resolved_scopes, excluded_scopes = scopes._resolve_requested_scopes(
|
||||
requested_scopes, have_scopes, self.orm_user, jupyterhub_client, db
|
||||
)
|
||||
if excluded_scopes:
|
||||
# what level should this be?
|
||||
# for admins-get-more use case, this is going to happen for most users
|
||||
# but for misconfiguration, folks will want to know!
|
||||
self.log.debug(
|
||||
"Not assigning requested scopes for %s: requested=%s, assigned=%s, excluded=%s",
|
||||
spawner._log_name,
|
||||
requested_scopes,
|
||||
resolved_scopes,
|
||||
excluded_scopes,
|
||||
)
|
||||
|
||||
api_token = self.new_api_token(note=note, scopes=resolved_scopes)
|
||||
|
||||
# pass requesting handler to the spawner
|
||||
# e.g. for processing GET params
|
||||
spawner.handler = handler
|
||||
@@ -808,6 +858,7 @@ class User:
|
||||
spawner.api_token,
|
||||
generated=False,
|
||||
note="retrieved from spawner %s" % server_name,
|
||||
scopes=resolved_scopes,
|
||||
)
|
||||
# update OAuth client secret with updated API token
|
||||
if oauth_provider:
|
||||
|
@@ -229,9 +229,10 @@ async def exponential_backoff(
|
||||
# add some random jitter to improve performance
|
||||
# this prevents overloading any single tornado loop iteration with
|
||||
# too many things
|
||||
dt = min(max_wait, remaining, random.uniform(0, start_wait * scale))
|
||||
if dt < max_wait:
|
||||
limit = min(max_wait, start_wait * scale)
|
||||
if limit < max_wait:
|
||||
scale *= scale_factor
|
||||
dt = min(remaining, random.uniform(0, limit))
|
||||
await asyncio.sleep(dt)
|
||||
raise asyncio.TimeoutError(fail_message)
|
||||
|
||||
|
@@ -43,7 +43,7 @@ target_version = [
|
||||
github_url = "https://github.com/jupyterhub/jupyterhub"
|
||||
|
||||
[tool.tbump.version]
|
||||
current = "4.0.0b1"
|
||||
current = "4.0.0"
|
||||
|
||||
# Example of a semver regexp.
|
||||
# Make sure this matches current_version before
|
||||
|
2
setup.py
2
setup.py
@@ -55,7 +55,7 @@ def get_package_data():
|
||||
'alembic/*',
|
||||
'alembic/versions/*',
|
||||
'event-schemas/*/*.yaml',
|
||||
'jupyterhub/singleuser/templates/*.html',
|
||||
'singleuser/templates/*.html',
|
||||
]
|
||||
return package_data
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Build as jupyterhub/singleuser
|
||||
# Run with the DockerSpawner in JupyterHub
|
||||
|
||||
ARG BASE_IMAGE=jupyter/base-notebook:2023-01-30
|
||||
ARG BASE_IMAGE=jupyter/base-notebook
|
||||
FROM $BASE_IMAGE
|
||||
MAINTAINER Project Jupyter <jupyter@googlegroups.com>
|
||||
|
||||
|
Reference in New Issue
Block a user