mirror of
https://github.com/jupyterhub/the-littlest-jupyterhub.git
synced 2025-12-18 21:54:05 +08:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e68745e38 | ||
|
|
a49f0a6889 | ||
|
|
bcfb426b5c | ||
|
|
59fb7fb8a2 | ||
|
|
95b54414ca | ||
|
|
eb5c0e4758 | ||
|
|
92e317d41f | ||
|
|
812be54635 | ||
|
|
3ddc1e8d59 | ||
|
|
a059d6ffbb | ||
|
|
856c647e88 | ||
|
|
a89021c49e | ||
|
|
a98299c629 | ||
|
|
e8602fc4a1 | ||
|
|
0de2db365a | ||
|
|
436c81f26d | ||
|
|
363dc45b52 | ||
|
|
1a9f3d65a4 | ||
|
|
3f55ac2912 | ||
|
|
92465ade36 | ||
|
|
2282e61894 | ||
|
|
94a16d6ddd | ||
|
|
7be7eb4968 | ||
|
|
0c322774e2 | ||
|
|
13c8946d8c | ||
|
|
f7118edf48 | ||
|
|
b034fc6713 | ||
|
|
3321a50f31 | ||
|
|
73f8833f0a | ||
|
|
5834f14b83 | ||
|
|
9111b73cee | ||
|
|
3bd739e8dc | ||
|
|
7a491e403c | ||
|
|
50c1b5f894 | ||
|
|
5c8cb5bb26 | ||
|
|
8490ef2949 | ||
|
|
5172ceb4f9 | ||
|
|
634956c33e | ||
|
|
5278351eb1 | ||
|
|
22e08a458b | ||
|
|
dcbb37688e | ||
|
|
c492c176bc | ||
|
|
65c5d78ea5 | ||
|
|
5abf657bdf | ||
|
|
c49fada1c8 | ||
|
|
708bbec2a4 | ||
|
|
729a3ceb28 | ||
|
|
9c7427923d | ||
|
|
bcc64e0d8b | ||
|
|
a5f966927f | ||
|
|
d0fb92eda5 | ||
|
|
52478abb65 | ||
|
|
96ac0d3538 | ||
|
|
13ed32b499 | ||
|
|
bdbb3adbbc | ||
|
|
2b29bd9f0a | ||
|
|
8fd41cc77a | ||
|
|
7e39e993da | ||
|
|
5469e21e74 | ||
|
|
38a01e8406 | ||
|
|
9bcfa70326 | ||
|
|
7474b876f1 | ||
|
|
5ae31ce169 | ||
|
|
b94a281ff8 | ||
|
|
67dd3c8abe | ||
|
|
c578a7bec0 | ||
|
|
51f8470535 | ||
|
|
196208ae58 | ||
|
|
242dca4376 | ||
|
|
5169301a0a | ||
|
|
4ddd798928 | ||
|
|
f921acc183 | ||
|
|
46e4045568 | ||
|
|
743f729902 | ||
|
|
48fe440372 | ||
|
|
8348d4ad96 | ||
|
|
579b7eb5ba | ||
|
|
0248785aed | ||
|
|
beed70060c | ||
|
|
d9a0a5fc7a | ||
|
|
4e397bc687 | ||
|
|
7d8a84860f | ||
|
|
2fe9912333 | ||
|
|
02c3d7539b | ||
|
|
10ba571bde | ||
|
|
7fa4e2bcec | ||
|
|
2faf0d3a5f | ||
|
|
b35851f2b7 | ||
|
|
9755938d7d | ||
|
|
1f7d6d1c55 | ||
|
|
9060267458 | ||
|
|
4912cffe65 | ||
|
|
d0c9aa263a | ||
|
|
fa363658df | ||
|
|
166eba6735 | ||
|
|
5a0de137d2 | ||
|
|
929536de7b | ||
|
|
ef5c6c56b7 | ||
|
|
fb01dea5e4 | ||
|
|
78d4b7fbc4 | ||
|
|
d292457803 | ||
|
|
6dd2ee812a | ||
|
|
641a02ed07 | ||
|
|
1c9301e4d7 | ||
|
|
723c4e756d | ||
|
|
eeae5f06be | ||
|
|
4abf3f003f | ||
|
|
3da9743640 | ||
|
|
aec331b114 | ||
|
|
0f5a0ccbe0 | ||
|
|
f1c7e2d90f | ||
|
|
5cb6b69020 | ||
|
|
7faf997c7b | ||
|
|
79134e3a2f | ||
|
|
e869633871 | ||
|
|
793f6237b4 | ||
|
|
caab46e08a | ||
|
|
dcb91d4e6a | ||
|
|
ed6ff645c2 | ||
|
|
291c096a17 | ||
|
|
eafc10e82a | ||
|
|
58a679f584 | ||
|
|
9bde7e4680 | ||
|
|
6359bf498c | ||
|
|
44bdaf2fa4 | ||
|
|
1d1aefd518 | ||
|
|
542cf4de6b | ||
|
|
46b49c819c | ||
|
|
c7507fd799 | ||
|
|
ff1f612d10 | ||
|
|
58181c9671 | ||
|
|
8d1033393c | ||
|
|
fc8f70463c |
12
.github/integration-test.py
vendored
12
.github/integration-test.py
vendored
@@ -10,7 +10,7 @@ GIT_REPO_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
TEST_IMAGE_NAME = "test-systemd"
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def _get_container_runtime_cli():
|
||||
runtimes = ["docker", "podman"]
|
||||
for runtime in runtimes:
|
||||
@@ -167,15 +167,23 @@ def run_test(
|
||||
command = f"python3 /srv/src/bootstrap/bootstrap.py --version={upgrade_from}"
|
||||
run_command(container_name, command)
|
||||
|
||||
# show user environment
|
||||
command = "/opt/tljh/user/bin/mamba list"
|
||||
run_command(container_name, command)
|
||||
|
||||
command = f"python3 /srv/src/bootstrap/bootstrap.py {' '.join(installer_args)}"
|
||||
run_command(container_name, command)
|
||||
|
||||
# show user environment (again if upgrade)
|
||||
command = "/opt/tljh/user/bin/mamba list"
|
||||
run_command(container_name, command)
|
||||
|
||||
# Install pkgs from requirements in hub's pip, where
|
||||
# the bootstrap script installed the others
|
||||
command = "/opt/tljh/hub/bin/python3 -m pip install -r /srv/src/integration-tests/requirements.txt"
|
||||
run_command(container_name, command)
|
||||
|
||||
# show environment
|
||||
# show hub environment
|
||||
command = "/opt/tljh/hub/bin/python3 -m pip freeze"
|
||||
run_command(container_name, command)
|
||||
|
||||
|
||||
21
.github/workflows/integration-test.yaml
vendored
21
.github/workflows/integration-test.yaml
vendored
@@ -36,12 +36,15 @@ jobs:
|
||||
- name: "Debian 11, Py 3.9"
|
||||
distro_image: "debian:11"
|
||||
extra_flags: ""
|
||||
- name: "Ubuntu 20.04, Py 3.8"
|
||||
distro_image: "ubuntu:20.04"
|
||||
- name: "Debian 12, Py 3.11"
|
||||
distro_image: "debian:12"
|
||||
extra_flags: ""
|
||||
- name: "Ubuntu 22.04 Py 3.10"
|
||||
distro_image: "ubuntu:22.04"
|
||||
extra_flags: ""
|
||||
- name: "Ubuntu 24.04 Py 3.12"
|
||||
distro_image: "ubuntu:24.04"
|
||||
extra_flags: ""
|
||||
- name: "Ubuntu 22.04, Py 3.10, from main"
|
||||
distro_image: "ubuntu:22.04"
|
||||
extra_flags: --upgrade-from=main
|
||||
@@ -51,10 +54,13 @@ jobs:
|
||||
- name: "Ubuntu 22.04, Py 3.10, from 0.2.0"
|
||||
distro_image: "ubuntu:22.04"
|
||||
extra_flags: --upgrade-from=0.2.0
|
||||
- name: "Ubuntu 22.04, Py 3.10, from 1.*"
|
||||
distro_image: "ubuntu:22.04"
|
||||
extra_flags: --upgrade-from=1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
@@ -116,10 +122,13 @@ jobs:
|
||||
distro_image: "ubuntu:22.04"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
cache: pip
|
||||
cache-dependency-path: |
|
||||
integration-tests/requirements.txt
|
||||
|
||||
# FIXME: The test_bootstrap.py script has duplicated logic to run build
|
||||
# and start images and run things in them. This makes tests slower,
|
||||
|
||||
27
.github/workflows/unit-test.yaml
vendored
27
.github/workflows/unit-test.yaml
vendored
@@ -46,40 +46,29 @@ jobs:
|
||||
- name: "Ubuntu 22.04, Py 3.10"
|
||||
ubuntu_version: "22.04"
|
||||
python_version: "3.10"
|
||||
- name: "Ubuntu 24.04, Py 3.12"
|
||||
ubuntu_version: "24.04"
|
||||
python_version: "3.12"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ matrix.python_version }}"
|
||||
|
||||
- name: Install venv, git and setup venv
|
||||
- name: Install venv, git, pip and setup venv
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --yes \
|
||||
python3-venv \
|
||||
python3-pip \
|
||||
bzip2 \
|
||||
git
|
||||
|
||||
python3 -m venv /srv/venv
|
||||
echo '/srv/venv/bin' >> $GITHUB_PATH
|
||||
|
||||
# WARNING: This action loads a cache of pip dependencies based on the
|
||||
# declared key, and it will save a cache for that key on job
|
||||
# completion. Make sure to update the key to bust the cache
|
||||
# properly if you make a change that should influence it.
|
||||
- name: Load cached Python dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /srv/venv/
|
||||
key: >-
|
||||
pip-
|
||||
${{ matrix.runs_on }}-
|
||||
${{ matrix.ubuntu_version }}-
|
||||
${{ matrix.python_version }}-
|
||||
${{ hashFiles('setup.py', 'dev-requirements.txt', '.github/workflows/unit-test.yaml') }}
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
pip install -r dev-requirements.txt
|
||||
@@ -93,4 +82,4 @@ jobs:
|
||||
run: pytest tests
|
||||
timeout-minutes: 15
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
- uses: codecov/codecov-action@v4
|
||||
|
||||
@@ -11,18 +11,18 @@
|
||||
repos:
|
||||
# Autoformat: Python code, syntax patterns are modernized
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.10.1
|
||||
rev: v3.17.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py36-plus
|
||||
# We need the bootstrap.py script to be parsable with Python 3.5, so we
|
||||
- --py39-plus
|
||||
# We need the bootstrap.py script to be parsable with Python 3.8, so we
|
||||
# exclude it from the pyupgrade hook that will apply f-strings etc.
|
||||
exclude: bootstrap/bootstrap.py
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.2.0
|
||||
rev: v2.3.1
|
||||
hooks:
|
||||
- id: autoflake
|
||||
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
||||
@@ -31,25 +31,25 @@ repos:
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.7.0
|
||||
rev: 24.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
# Autoformat: markdown, yaml
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0
|
||||
rev: v4.0.0-alpha.8
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
# Misc...
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.6.0
|
||||
# ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available
|
||||
hooks:
|
||||
# Autoformat: Makes sure files end in a newline and only a newline.
|
||||
@@ -64,7 +64,7 @@ repos:
|
||||
|
||||
# Lint: Python code
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: "6.1.0"
|
||||
rev: "7.1.1"
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ This script is run as:
|
||||
|
||||
Constraints:
|
||||
|
||||
- The entire script should be compatible with Python 3.8, which is the default on
|
||||
Ubuntu 20.04.
|
||||
- The script should parse in Python 3.6 as we print error messages for using
|
||||
Ubuntu 18.04 which comes with Python 3.6 by default.
|
||||
- The entire script should be compatible with Python 3.9, which is the default on
|
||||
Debian 11.
|
||||
- The script should parse in Python 3.8 as we print error messages for using
|
||||
Ubuntu 20.04 which comes with Python 3.8 by default.
|
||||
- The script must depend only on stdlib modules, as no previous installation
|
||||
of dependencies can be assumed.
|
||||
|
||||
@@ -42,6 +42,7 @@ Command line flags, from "bootstrap.py --help":
|
||||
can also pass a branch name such as 'main' or a
|
||||
commit hash.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
@@ -209,22 +210,22 @@ def ensure_host_system_can_install_tljh():
|
||||
Check if TLJH is installable in current host system and exit with a clear
|
||||
error message otherwise.
|
||||
"""
|
||||
# Require Ubuntu 20.04+ or Debian 11+
|
||||
# Require Ubuntu 22.04+ or Debian 11+
|
||||
distro = get_os_release_variable("ID")
|
||||
version = get_os_release_variable("VERSION_ID")
|
||||
if distro not in ["ubuntu", "debian"]:
|
||||
print("The Littlest JupyterHub currently supports Ubuntu or Debian Linux only")
|
||||
sys.exit(1)
|
||||
elif distro == "ubuntu" and _parse_version(version) < (20, 4):
|
||||
print("The Littlest JupyterHub requires Ubuntu 20.04 or higher")
|
||||
elif distro == "ubuntu" and _parse_version(version) < (22, 4):
|
||||
print("The Littlest JupyterHub requires Ubuntu 22.04 or higher")
|
||||
sys.exit(1)
|
||||
elif distro == "debian" and _parse_version(version) < (11,):
|
||||
print("The Littlest JupyterHub requires Debian 11 or higher")
|
||||
sys.exit(1)
|
||||
|
||||
# Require Python 3.8+
|
||||
if sys.version_info < (3, 8):
|
||||
print(f"bootstrap.py must be run with at least Python 3.8, found {sys.version}")
|
||||
# Require Python 3.9+
|
||||
if sys.version_info < (3, 9):
|
||||
print(f"bootstrap.py must be run with at least Python 3.9, found {sys.version}")
|
||||
sys.exit(1)
|
||||
|
||||
# Require systemd (systemctl is a part of systemd)
|
||||
|
||||
@@ -89,6 +89,7 @@ If so, you can tell your deployment to use these files:
|
||||
sudo tljh-config set https.enabled true
|
||||
sudo tljh-config set https.tls.key /etc/mycerts/mydomain.key
|
||||
sudo tljh-config set https.tls.cert /etc/mycerts/mydomain.cert
|
||||
sudo tljh-config add-item https.tls.domains yourhub.yourdomain.edu
|
||||
```
|
||||
|
||||
Once you have loaded this, your config should look like:
|
||||
@@ -103,6 +104,8 @@ https:
|
||||
tls:
|
||||
key: /etc/mycerts/mydomain.key
|
||||
cert: /etc/mycerts/mydomain.cert
|
||||
domains:
|
||||
- yourhub.yourdomain.edu
|
||||
```
|
||||
|
||||
Finally, you can reload the proxy to load the new configuration:
|
||||
|
||||
@@ -23,7 +23,7 @@ PrivateDevices=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
Environment=TLJH_INSTALL_PREFIX=/opt/tljh
|
||||
ExecStart=/opt/tljh/hub/bin/python3 -m jupyterhub.app -f jupyterhub_config.py --upgrade-db
|
||||
ExecStart=/opt/tljh/hub/bin/python3 -m jupyterhub -f jupyterhub_config.py --upgrade-db
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -149,9 +149,10 @@ If it is not provided as array, there is an easy fix. Just add these lines to
|
||||
your `awscognito.py`:
|
||||
|
||||
```python
|
||||
def claim_groups_key_func(user_data_resp_json):
|
||||
return [user_data_resp_json['custom:department']]
|
||||
def groups_key_func(auth_state):
|
||||
return [auth_state['oauth_user']['custom:department']]
|
||||
|
||||
c.GenericOAuthenticator.claim_groups_key = claim_groups_key_func
|
||||
c.GenericOAuthenticator.manage_groups = True
|
||||
c.GenericOAuthenticator.auth_state_groups_key = groups_key_func
|
||||
c.GenericOAuthenticator.allowed_groups = ["AA BB CC", "AA BB DD"]
|
||||
```
|
||||
|
||||
@@ -91,10 +91,16 @@ For more information on `tljh-config`, see [](/topic/tljh-config).
|
||||
4. Tell your JupyterHub to _use_ the GitHub OAuthenticator for authentication:
|
||||
|
||||
```
|
||||
sudo tljh-config set auth.type oauthenticator.github.GitHubOAuthenticator
|
||||
sudo tljh-config set auth.type github
|
||||
```
|
||||
|
||||
5. Restart your JupyterHub so that new users see these changes:
|
||||
5. Tell JupyterHub which users to allow, if you haven't already:
|
||||
|
||||
```
|
||||
sudo tljh-config add-item users.allowed good-user_1
|
||||
```
|
||||
|
||||
6. Restart your JupyterHub so that new users see these changes:
|
||||
|
||||
```
|
||||
sudo tljh-config reload
|
||||
|
||||
@@ -42,4 +42,4 @@ tljh-config reload
|
||||
|
||||
## Optional features
|
||||
|
||||
More optional features are available on the `authenticator documentation <https://native-authenticator.readthedocs.io/en/latest/>`
|
||||
More optional features are available on the [authenticator documentation](https://native-authenticator.readthedocs.io/en/latest/)
|
||||
|
||||
BIN
docs/images/control-panel-menu.png
Normal file
BIN
docs/images/control-panel-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -1,11 +1,10 @@
|
||||
Most administration & configuration of the JupyterHub can be done from the
|
||||
web UI directly. Let's add a few users who can log in!
|
||||
|
||||
1. Open the **Control Panel** by clicking the control panel button on the top
|
||||
right of your JupyterHub.
|
||||
1. In the File menu select the entry for the **Hub Control Panel**.
|
||||
|
||||
```{image} ../images/control-panel-button.png
|
||||
:alt: Control panel button in notebook, top right
|
||||
```{image} ../images/control-panel-menu.png
|
||||
:alt: Hub Control panel entry in lab File menu
|
||||
```
|
||||
|
||||
2. In the control panel, open the **Admin** link in the top left.
|
||||
|
||||
@@ -139,10 +139,10 @@ Let's create the server on which we can run JupyterHub.
|
||||
SSH to connect (port 22).
|
||||
|
||||
If you have never used your Amazon account before, you'll have to select
|
||||
**Create a new security group**. You should give it a disitnguishing name
|
||||
**Create a new security group**. You should give it a distinctive name
|
||||
under **Security group name**
|
||||
such as `ssh_web` for future reference. If you have, one from before you can
|
||||
select it and adjust it to have the rules you need, if you prefer.
|
||||
such as `ssh_web` for future reference. If you already have a security group,
|
||||
you can select it and adjust it to have the rules you need, if you prefer.
|
||||
|
||||
The rules will default to include `SSH`. Leave that there, and then click on
|
||||
the **Add Rule** button. Under **Type** for the new rule, change the field
|
||||
|
||||
@@ -2,6 +2,129 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## 2.0
|
||||
|
||||
### 2.0.0b1 - 2024-09-30
|
||||
|
||||
This release bundles with the latest available software from the JupyterHub
|
||||
ecosystem.
|
||||
|
||||
For instructions on how to make an upgrade, see [](howto-admin-upgrade-tljh).
|
||||
|
||||
#### Breaking changes
|
||||
|
||||
- JupyterHub 4.\* has been upgraded to >=5.1.0,<6
|
||||
- Refer to the [JupyterHub changelog] for details and pay attention to the
|
||||
entries for JupyterHub version 5.0.0.
|
||||
- OAuthenticator 16.0.4 has been upgraded to >=17.0.0,<18
|
||||
- If you are using an OAuthenticator based authenticator class
|
||||
(GitHubOAuthenticator, GoogleOAuthenticator, ...), refer to the
|
||||
[OAuthenticator changelog] for details and pay attention to the entries for
|
||||
JupyterHub version 17.0.0.
|
||||
- LDAPAuthenticator 1.3.2 has been upgraded to >=2.0.0,<3
|
||||
- If you are using this authenticator class, refer to the [LDAPAuthenticator
|
||||
changelog] for details and pay attention to the entries for
|
||||
LDAPAuthenticator version 2.0.0.
|
||||
- The configured JupyterHub Proxy class `traefik-proxy` and the `traefik` server
|
||||
controlled by JupyterHub via the proxy class has been upgraded to a new major
|
||||
version, but no breaking change are expected to be noticed for users.
|
||||
|
||||
[oauthenticator changelog]: https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html
|
||||
[ldapauthenticator changelog]: https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md
|
||||
|
||||
#### Notable dependencies updated
|
||||
|
||||
A TLJH installation provides a Python environment where the software for
|
||||
JupyterHub itself runs - _the hub environment_, and a Python environment where
|
||||
the software of users runs - _the user environment_.
|
||||
|
||||
If you are installing TLJH for the first time, the user environment will be
|
||||
setup initially with Python 3.12 and some other packages described in
|
||||
[tljh/requirements-user-env-extras.txt].
|
||||
|
||||
If you are upgrading to this version of TLJH, the bare minimum is changed in the
|
||||
user environment. The hub environment's dependencies are on the other hand
|
||||
always upgraded to the latest version within the specified version range defined
|
||||
in [tljh/requirements-hub-env.txt] and seen below.
|
||||
|
||||
[tljh/requirements-user-env-extras.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/2.0.0b1/tljh/requirements-user-env-extras.txt
|
||||
[tljh/requirements-hub-env.txt]: https://github.com/jupyterhub/the-littlest-jupyterhub/blob/2.0.0b1/tljh/requirements-hub-env.txt
|
||||
|
||||
The changes in the respective environments between TLJH version 1.0.0 and 2.0.0b1
|
||||
are summarized below.
|
||||
|
||||
| Dependency changes in the _hub environment_ | Version in 1.0.0 | Version in 2.0.0b1 | Changelog link | Note |
|
||||
| ------------------------------------------------------------------------------ | ---------------- | ------------------ | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.1.0,<6 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Running in the `jupyterhub` systemd service |
|
||||
| [traefik](https://github.com/traefik/traefik) | 2.10.1 | 3.1.4 | [Changelog](https://github.com/traefik/traefik/blob/master/CHANGELOG.md) | Running in the `traefik` systemd service |
|
||||
| [traefik-proxy](https://github.com/jupyterhub/traefik-proxy) | >=1.1.0,<2 | 2.\* | [Changelog](https://jupyterhub-traefik-proxy.readthedocs.io/en/latest/changelog.html) | Run by jupyterhub, controls `traefik` |
|
||||
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | >=1.0.1,<2 | >=1.0.1,<2 | [Changelog](https://github.com/jupyterhub/systemdspawner/blob/master/CHANGELOG.md) | Run by jupyterhub, controls user servers via systemd |
|
||||
| [jupyterhub-idle-culler](https://github.com/jupyterhub/jupyterhub-idle-culler) | >=1.2.1,<2 | >=1.4.0,<2 | [Changelog](https://github.com/jupyterhub/jupyterhub-idle-culler/blob/main/CHANGELOG.md) | Run by jupyterhub, stops inactivate servers etc. |
|
||||
| [firstuseauthenticator](https://github.com/jupyterhub/firstuseauthenticator) | >=1.0.0,<2 | 1.1.0,<2 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||
| [tmpauthenticator](https://github.com/jupyterhub/tmpauthenticator) | >=1.0.0,<2 | 1.0.0,<2 | [Changelog](https://github.com/jupyterhub/tmpauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||
| [nativeauthenticator](https://github.com/jupyterhub/nativeauthenticator) | >=1.2.0,<2 | >=1.3.0,<2 | [Changelog](https://github.com/jupyterhub/nativeauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||
| [oauthenticator](https://github.com/jupyterhub/oauthenticator) | >=16.0.4,<17 | >=17.0.0,<18 | [Changelog](https://oauthenticator.readthedocs.io/en/latest/reference/changelog.html) | An optional way to authenticate users |
|
||||
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | >=1.3.2,<2 | ==2.0.0b2 | [Changelog](https://github.com/jupyterhub/ldapauthenticator/blob/HEAD/CHANGELOG.md) | An optional way to authenticate users |
|
||||
| [pip](https://github.com/pypa/pip) | >=23.1.2 | >=23.1.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | - |
|
||||
|
||||
| Dependency changes in the _user environment_ | Version in 1.0.0 | Version in upgrade to 2.0.0b1 | Version in fresh install of 2.0.0b1 | Changelog link | Note |
|
||||
| -------------------------------------------------------- | ---------------- | ----------------------------- | ----------------------------------- | --------------------------------------------------------------------------------- | ------------------------ |
|
||||
| [jupyterhub](https://github.com/jupyterhub/jupyterhub) | >=4.0.2,<5 | >=5.1.0,<6 | >=5.1.0,<6 | [Changelog](https://jupyterhub.readthedocs.io/en/stable/reference/changelog.html) | Always upgraded. |
|
||||
| [pip](https://github.com/pypa/pip) | >=23.1.2 | >=23.1.2 | >=24.2 | [Changelog](https://pip.pypa.io/en/stable/news/) | Only upgraded if needed. |
|
||||
| [conda](https://docs.conda.io/projects/conda/en/stable/) | >=4.10.0 | >=4.10.0 | ==24.7.1 | [Changelog](https://docs.conda.io/projects/conda/en/stable/release-notes.html) | Only upgraded if needed. |
|
||||
| [mamba](https://mamba.readthedocs.io/en/latest/) | >=0.16.0 | >=0.16.0 | ==1.5.9 | [Changelog](https://github.com/mamba-org/mamba/blob/main/CHANGELOG.md) | Only upgraded if needed. |
|
||||
|
||||
#### New features added
|
||||
|
||||
- jupyterhub 5 [#989](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/989) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- Validate tljh specific config [#962](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/962) ([@jrdnbradford](https://github.com/jrdnbradford), [@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- Add the ability to define conda channels in plugins via `tljh_extra_user_conda_channels` [#942](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/942) ([@yuvipanda](https://github.com/yuvipanda), [@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Bugs fixed
|
||||
|
||||
- fix `-m` invocation of jupyterhub [#988](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/988) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- Re-install conda/mamba for every tljh upgrade (doesn't imply upgrade) [#968](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/968) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- Add missing oauthenticator dependency for AzureADOAuthenticator [#959](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/959) ([@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Maintenance and upkeep improvements
|
||||
|
||||
- Bump the requirements-user-env-extras.txt lower version bounds [#1002](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1002) ([@consideRatio](https://github.com/consideRatio))
|
||||
- Update traefik from 2.10.1 to 3.1.4 [#1001](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/1001) ([@consideRatio](https://github.com/consideRatio))
|
||||
- Update to install miniforge 24.7.1-2 from 24.7.1-0 [#999](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/999) ([@consideRatio](https://github.com/consideRatio))
|
||||
- Drop ubuntu 20.04, require py39, traefik-proxy v2, and ldapauthenticator v2 [#998](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/998) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- consolidate lock file handling [#994](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/994) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@jrdnbradford](https://github.com/jrdnbradford))
|
||||
- update oauthenticator to 17 [#992](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/992) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Update base user environment to miniforge 24.7.1-0 (Python 3.12) [#990](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/990) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Add TLJH config lockfile [#976](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/976) ([@jrdnbradford](https://github.com/jrdnbradford), [@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- tests: fix to catch test failure earlier when they really happen [#975](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/975) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
|
||||
#### Documentation improvements
|
||||
|
||||
- Added missing details on how to add custom domain from manual HTTPS configuration [#983](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/983) ([@josedaudi](https://github.com/josedaudi), [@yuvipanda](https://github.com/yuvipanda))
|
||||
- Reword documentation sentence [#970](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/970) ([@davidalber](https://github.com/davidalber), [@consideRatio](https://github.com/consideRatio))
|
||||
- Fix typo and replace word [#969](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/969) ([@davidalber](https://github.com/davidalber), [@consideRatio](https://github.com/consideRatio))
|
||||
- Fix URL syntax in nativeauth.md [#949](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/949) ([@pdebuyl](https://github.com/pdebuyl), [@minrk](https://github.com/minrk))
|
||||
- adapt install documentation for new /lab default interface [#935](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/935) ([@schwebke](https://github.com/schwebke), [@minrk](https://github.com/minrk))
|
||||
|
||||
#### Continuous integration improvements
|
||||
|
||||
- ci: cache pip only when no container is used [#997](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/997) ([@consideRatio](https://github.com/consideRatio))
|
||||
- ci: run unit tests in ubuntu-24.04 github actions environment as well [#993](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/993) ([@consideRatio](https://github.com/consideRatio))
|
||||
- ci: add tests for debian 12 and ubuntu 24.04 [#967](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/967) ([@consideRatio](https://github.com/consideRatio))
|
||||
- build(deps): bump actions/cache from 3 to 4 [#961](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/961) ([@consideRatio](https://github.com/consideRatio))
|
||||
- build(deps): bump codecov/codecov-action from 3 to 4 [#960](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/960) ([@consideRatio](https://github.com/consideRatio))
|
||||
- build(deps): bump actions/setup-python from 4 to 5 [#958](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/958) ([@consideRatio](https://github.com/consideRatio))
|
||||
- build(deps): bump actions/checkout from 3 to 4 [#943](https://github.com/jupyterhub/the-littlest-jupyterhub/pull/943) ([@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Contributors to this release
|
||||
|
||||
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/the-littlest-jupyterhub/graphs/contributors?from=2023-08-11&to=2024-09-30&type=c))
|
||||
|
||||
@consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AconsideRatio+updated%3A2023-08-11..2024-09-30&type=Issues)) | @davidalber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Adavidalber+updated%3A2023-08-11..2024-09-30&type=Issues)) | @josedaudi ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajosedaudi+updated%3A2023-08-11..2024-09-30&type=Issues)) | @jrdnbradford ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ajrdnbradford+updated%3A2023-08-11..2024-09-30&type=Issues)) | @kiliansinger ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Akiliansinger+updated%3A2023-08-11..2024-09-30&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Amanics+updated%3A2023-08-11..2024-09-30&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aminrk+updated%3A2023-08-11..2024-09-30&type=Issues)) | @MridulS ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3AMridulS+updated%3A2023-08-11..2024-09-30&type=Issues)) | @pdebuyl ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Apdebuyl+updated%3A2023-08-11..2024-09-30&type=Issues)) | @schwebke ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Aschwebke+updated%3A2023-08-11..2024-09-30&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fthe-littlest-jupyterhub+involves%3Ayuvipanda+updated%3A2023-08-11..2024-09-30&type=Issues))
|
||||
|
||||
## 1.0
|
||||
|
||||
### 1.0.0 - 2023-08-11
|
||||
|
||||
@@ -26,7 +26,7 @@ sudo rm -rf /opt/tljh/hub
|
||||
|
||||
## User environment
|
||||
|
||||
By default, a `mambaforge` conda environment is installed in `/opt/tljh/user`. This contains
|
||||
By default, a `miniforge` conda environment is installed in `/opt/tljh/user`. This contains
|
||||
the notebook interface used to launch all users, and the various packages available to all
|
||||
users. The environment is owned by the `root` user. JupyterHub admins may use
|
||||
to `sudo -E conda install` or `sudo -E pip install` packages into this environment.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Systemd inside a Docker container, for CI only
|
||||
ARG BASE_IMAGE=ubuntu:20.04
|
||||
ARG BASE_IMAGE=ubuntu:22.04
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
# DEBIAN_FRONTEND is set to avoid being asked for input and hang during build:
|
||||
@@ -29,8 +29,8 @@ RUN systemctl set-default multi-user.target
|
||||
STOPSIGNAL SIGRTMIN+3
|
||||
|
||||
# Uncomment these lines for a development install
|
||||
#ENV TLJH_BOOTSTRAP_DEV=yes
|
||||
#ENV TLJH_BOOTSTRAP_PIP_SPEC=/srv/src
|
||||
#ENV PATH=/opt/tljh/hub/bin:${PATH}
|
||||
# ENV TLJH_BOOTSTRAP_DEV=yes
|
||||
# ENV TLJH_BOOTSTRAP_PIP_SPEC=/srv/src
|
||||
# ENV PATH=/opt/tljh/hub/bin:${PATH}
|
||||
|
||||
CMD ["/bin/bash", "-c", "exec /lib/systemd/systemd --log-target=journal 3>&1"]
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
"""
|
||||
Simplest plugin that exercises all the hooks defined in tljh/hooks.py.
|
||||
"""
|
||||
|
||||
from tljh.hooks import hookimpl
|
||||
|
||||
|
||||
@hookimpl
|
||||
def tljh_extra_user_conda_packages():
|
||||
return ["tqdm"]
|
||||
# tqdm installs from the conda-forge channel (https://conda-forge.org/packages/)
|
||||
# csvtk installs from the bioconda channel (https://bioconda.github.io/conda-package_index.html)
|
||||
return ["tqdm", "csvtk"]
|
||||
|
||||
|
||||
@hookimpl
|
||||
def tljh_extra_user_conda_channels():
|
||||
return ["conda-forge", "bioconda"]
|
||||
|
||||
|
||||
@hookimpl
|
||||
def tljh_extra_user_pip_packages():
|
||||
return ["django"]
|
||||
return ["simplejson"]
|
||||
|
||||
|
||||
@hookimpl
|
||||
|
||||
@@ -9,6 +9,7 @@ FIXME: The last test stands out and could be part of the other tests, and the
|
||||
first two could be more like unit tests. Ideally, this file is
|
||||
significantly reduced.
|
||||
"""
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
import subprocess
|
||||
@@ -84,9 +85,9 @@ def test_ubuntu_too_old():
|
||||
"""
|
||||
Error with a useful message when running in older Ubuntu
|
||||
"""
|
||||
output = _run_bootstrap_in_container("ubuntu:18.04", False)
|
||||
output = _run_bootstrap_in_container("ubuntu:20.04", False)
|
||||
_stop_container()
|
||||
assert output.stdout == "The Littlest JupyterHub requires Ubuntu 20.04 or higher\n"
|
||||
assert output.stdout == "The Littlest JupyterHub requires Ubuntu 22.04 or higher\n"
|
||||
assert output.returncode == 1
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ def test_labextensions():
|
||||
# jupyter-labextension writes to stdout and stderr weirdly
|
||||
proc = subprocess.run(
|
||||
["/opt/tljh/user/bin/jupyter-labextension", "list"],
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
extensions = [
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_hub_version():
|
||||
r = requests.get(HUB_URL + "/hub/api")
|
||||
r.raise_for_status()
|
||||
info = r.json()
|
||||
assert V("4") <= V(info["version"]) <= V("5")
|
||||
assert V("5.1") <= V(info["version"]) <= V("6")
|
||||
|
||||
|
||||
async def test_user_code_execute():
|
||||
@@ -59,9 +59,9 @@ async def test_user_code_execute():
|
||||
|
||||
async with User(username, HUB_URL, partial(login_dummy, password="")) as u:
|
||||
assert await u.login()
|
||||
await u.ensure_server_simulate(timeout=60, spawn_refresh_time=5)
|
||||
await u.start_kernel()
|
||||
await u.assert_code_output("5 * 4", "20", 5, 5)
|
||||
assert await u.ensure_server_simulate(timeout=60, spawn_refresh_time=5)
|
||||
assert await u.start_kernel()
|
||||
assert await u.assert_code_output("5 * 4", "20", 5, 5)
|
||||
|
||||
|
||||
async def test_user_server_started_with_custom_base_url():
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""tests for the proxy"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import ssl
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Test the plugin in integration-tests/plugins/simplest that makes use of all tljh
|
||||
recognized plugin hooks that are defined in tljh/hooks.py.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -19,13 +20,24 @@ def test_tljh_extra_user_conda_packages():
|
||||
|
||||
|
||||
def test_tljh_extra_user_pip_packages():
|
||||
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import django"])
|
||||
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import simplejson"])
|
||||
|
||||
|
||||
def test_tljh_extra_hub_pip_packages():
|
||||
subprocess.check_call([f"{HUB_ENV_PREFIX}/bin/python3", "-c", "import there"])
|
||||
|
||||
|
||||
def test_conda_packages():
|
||||
"""
|
||||
Test extra user conda packages are installed from multiple channels.
|
||||
|
||||
- tqdm installs from the conda-forge channel (https://conda-forge.org/packages/)
|
||||
- csvtk installs from the bioconda channel (https://bioconda.github.io/conda-package_index.html)
|
||||
"""
|
||||
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/python3", "-c", "import tqdm"])
|
||||
subprocess.check_call([f"{USER_ENV_PREFIX}/bin/csvtk", "cat", "--help"])
|
||||
|
||||
|
||||
def test_tljh_extra_apt_packages():
|
||||
assert os.path.exists("/usr/games/sl")
|
||||
|
||||
|
||||
@@ -25,12 +25,10 @@ profile = "black"
|
||||
# target-version should be all supported versions, see
|
||||
# https://github.com/psf/black/issues/751#issuecomment-473066811
|
||||
target_version = [
|
||||
"py36",
|
||||
"py37",
|
||||
"py38",
|
||||
"py39",
|
||||
"py310",
|
||||
"py311",
|
||||
"py312",
|
||||
]
|
||||
|
||||
|
||||
@@ -67,7 +65,7 @@ omit = [
|
||||
github_url = "https://github.com/jupyterhub/the-littlest-jupyterhub"
|
||||
|
||||
[tool.tbump.version]
|
||||
current = "1.0.0"
|
||||
current = "2.0.0b1"
|
||||
regex = '''
|
||||
(?P<major>\d+)
|
||||
\.
|
||||
|
||||
8
setup.py
8
setup.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name="the-littlest-jupyterhub",
|
||||
version="1.0.0",
|
||||
version="2.0.0b1",
|
||||
description="A small JupyterHub distribution",
|
||||
url="https://github.com/jupyterhub/the-littlest-jupyterhub",
|
||||
author="Jupyter Development Team",
|
||||
@@ -10,14 +10,16 @@ setup(
|
||||
license="3 Clause BSD",
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
python_requires=">=3.9",
|
||||
install_requires=[
|
||||
"ruamel.yaml==0.17.*",
|
||||
"ruamel.yaml==0.18.*",
|
||||
"jinja2",
|
||||
"pluggy==1.*",
|
||||
"backoff",
|
||||
"filelock",
|
||||
"requests",
|
||||
"bcrypt",
|
||||
"jupyterhub-traefik-proxy==1.*",
|
||||
"jupyterhub-traefik-proxy==2.*",
|
||||
],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""pytest fixtures"""
|
||||
|
||||
import os
|
||||
import types
|
||||
from importlib import reload
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Test conda commandline wrappers
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -13,9 +14,9 @@ from tljh import conda, installer
|
||||
@pytest.fixture(scope="module")
|
||||
def prefix():
|
||||
"""
|
||||
Provide a temporary directory with a mambaforge conda environment
|
||||
Provide a temporary directory with a conda environment
|
||||
"""
|
||||
installer_url, checksum = installer._mambaforge_url()
|
||||
installer_url, checksum = installer._miniforge_url()
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with conda.download_miniconda_installer(
|
||||
installer_url, checksum
|
||||
@@ -33,6 +34,15 @@ def test_ensure_packages(prefix):
|
||||
subprocess.check_call([os.path.join(prefix, "bin", "python"), "-c", "import numpy"])
|
||||
|
||||
|
||||
def test_ensure_channel_packages(prefix):
|
||||
"""
|
||||
Test installing packages in conda environment
|
||||
"""
|
||||
conda.ensure_conda_packages(prefix, ["csvtk"], channels=("conda-forge", "bioconda"))
|
||||
# Throws an error if this fails
|
||||
subprocess.check_call([os.path.join(prefix, "bin", "csvtk"), "cat", "--help"])
|
||||
|
||||
|
||||
def test_ensure_pip_packages(prefix):
|
||||
"""
|
||||
Test installing pip packages in conda environment
|
||||
|
||||
@@ -143,21 +143,24 @@ def test_remove_from_config_error():
|
||||
|
||||
|
||||
def test_reload_hub():
|
||||
with mock.patch("tljh.systemd.restart_service") as restart_service, mock.patch(
|
||||
"tljh.systemd.check_service_active"
|
||||
) as check_active, mock.patch("tljh.config.check_hub_ready") as check_ready:
|
||||
with (
|
||||
mock.patch("tljh.systemd.restart_service") as restart_service,
|
||||
mock.patch("tljh.systemd.check_service_active") as check_active,
|
||||
mock.patch("tljh.config.check_hub_ready") as check_ready,
|
||||
):
|
||||
config.reload_component("hub")
|
||||
assert restart_service.called_with("jupyterhub")
|
||||
assert check_active.called_with("jupyterhub")
|
||||
restart_service.assert_called_with("jupyterhub")
|
||||
check_active.assert_called_with("jupyterhub")
|
||||
|
||||
|
||||
def test_reload_proxy(tljh_dir):
|
||||
with mock.patch("tljh.systemd.restart_service") as restart_service, mock.patch(
|
||||
"tljh.systemd.check_service_active"
|
||||
) as check_active:
|
||||
with (
|
||||
mock.patch("tljh.systemd.restart_service") as restart_service,
|
||||
mock.patch("tljh.systemd.check_service_active") as check_active,
|
||||
):
|
||||
config.reload_component("proxy")
|
||||
assert restart_service.called_with("traefik")
|
||||
assert check_active.called_with("traefik")
|
||||
restart_service.assert_called_with("traefik")
|
||||
check_active.assert_called_with("traefik")
|
||||
assert os.path.exists(os.path.join(config.STATE_DIR, "traefik.toml"))
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Unit test functions in installer.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from subprocess import PIPE, run
|
||||
@@ -45,12 +46,12 @@ def test_ensure_admins(tljh_dir, admins, expected_config):
|
||||
|
||||
|
||||
def setup_conda(distro, version, prefix):
|
||||
"""Install mambaforge or miniconda in a prefix"""
|
||||
"""Install miniforge or miniconda in a prefix"""
|
||||
if distro == "mambaforge":
|
||||
installer_url, _ = installer._mambaforge_url(version)
|
||||
installer_url, _ = installer._miniforge_url(version)
|
||||
installer_url = installer_url.replace("Miniforge3", "Mambaforge")
|
||||
elif distro == "miniforge":
|
||||
installer_url, _ = installer._mambaforge_url(version)
|
||||
installer_url = installer_url.replace("Mambaforge", "Miniforge3")
|
||||
installer_url, _ = installer._miniforge_url(version)
|
||||
elif distro == "miniconda":
|
||||
arch = os.uname().machine
|
||||
installer_url = (
|
||||
@@ -123,9 +124,9 @@ def _specifier(version):
|
||||
None,
|
||||
None,
|
||||
{
|
||||
"python": "3.10.*",
|
||||
"conda": "23.1.0",
|
||||
"mamba": "1.4.1",
|
||||
"python": "3.12.*",
|
||||
"conda": "24.7.1",
|
||||
"mamba": "1.5.9",
|
||||
},
|
||||
),
|
||||
# previous install, 1.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Unit test functions in installer.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import date
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Test functions for normalizing various kinds of values
|
||||
"""
|
||||
|
||||
from tljh.normalize import generate_system_username
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test traefik configuration"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Test wrappers in tljw.user module
|
||||
"""
|
||||
|
||||
import grp
|
||||
import os
|
||||
import os.path
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Utilities for working with the apt package manager
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Wrap conda commandline program
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
@@ -98,9 +99,11 @@ def install_miniconda(installer_path, prefix):
|
||||
fix_permissions(prefix)
|
||||
|
||||
|
||||
def ensure_conda_packages(prefix, packages, force_reinstall=False):
|
||||
def ensure_conda_packages(
|
||||
prefix, packages, channels=("conda-forge",), force_reinstall=False
|
||||
):
|
||||
"""
|
||||
Ensure packages (from conda-forge) are installed in the conda prefix.
|
||||
Ensure packages (from channels) are installed in the conda prefix.
|
||||
|
||||
Note that conda seem to update dependencies by default, so there is probably
|
||||
no need to have a update parameter exposed for this function.
|
||||
@@ -117,13 +120,14 @@ def ensure_conda_packages(prefix, packages, force_reinstall=False):
|
||||
# avoids problems with RemoveError upgrading conda from old versions
|
||||
cmd += ["--force-reinstall"]
|
||||
|
||||
for channel in channels:
|
||||
cmd += ["-c", channel]
|
||||
|
||||
abspath = os.path.abspath(prefix)
|
||||
|
||||
utils.run_subprocess(
|
||||
cmd
|
||||
+ [
|
||||
"-c",
|
||||
"conda-forge", # Make customizable if we ever need to
|
||||
"--prefix",
|
||||
abspath,
|
||||
]
|
||||
|
||||
158
tljh/config.py
158
tljh/config.py
@@ -18,9 +18,11 @@ import re
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
|
||||
import requests
|
||||
from filelock import FileLock, Timeout
|
||||
|
||||
from .yaml import yaml
|
||||
|
||||
@@ -32,6 +34,22 @@ CONFIG_DIR = os.path.join(INSTALL_PREFIX, "config")
|
||||
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def config_file_lock(config_path, timeout=1):
|
||||
"""Context manager to acquire the config file lock"""
|
||||
lock_file = f"{config_path}.lock"
|
||||
try:
|
||||
with FileLock(lock_file).acquire(timeout=timeout):
|
||||
yield
|
||||
|
||||
except Timeout:
|
||||
print(
|
||||
f"Another instance of tljh-config holds the lock {lock_file}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def set_item_in_config(config, property_path, value):
|
||||
"""
|
||||
Set key at property_path to value in config & return new config.
|
||||
@@ -154,92 +172,102 @@ def remove_item_from_config(config, property_path, value):
|
||||
return config_copy
|
||||
|
||||
|
||||
def validate_config(config, validate):
|
||||
"""
|
||||
Validate changes to the config with tljh-config against the schema
|
||||
"""
|
||||
import jsonschema
|
||||
|
||||
from .config_schema import config_schema
|
||||
|
||||
try:
|
||||
jsonschema.validate(instance=config, schema=config_schema)
|
||||
except jsonschema.exceptions.ValidationError as e:
|
||||
if validate:
|
||||
print(
|
||||
f"Config validation error: {e.message}.\n"
|
||||
"You can still apply this change without validation by re-running your command with the --no-validate flag.\n"
|
||||
"If you think this validation error is incorrect, please report it to https://github.com/jupyterhub/the-littlest-jupyterhub/issues.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_config(config_path):
|
||||
"""
|
||||
Pretty print config from given config_path
|
||||
"""
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config = yaml.load(f)
|
||||
except FileNotFoundError:
|
||||
config = {}
|
||||
|
||||
config = get_current_config(config_path)
|
||||
yaml.dump(config, sys.stdout)
|
||||
|
||||
|
||||
def set_config_value(config_path, key_path, value):
|
||||
def set_config_value(config_path, key_path, value, validate=True):
|
||||
"""
|
||||
Set key at key_path in config_path to value
|
||||
"""
|
||||
# FIXME: Have a file lock here
|
||||
# FIXME: Validate schema here
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config = yaml.load(f)
|
||||
except FileNotFoundError:
|
||||
config = {}
|
||||
with config_file_lock(config_path):
|
||||
config = get_current_config(config_path)
|
||||
config = set_item_in_config(config, key_path, value)
|
||||
validate_config(config, validate)
|
||||
|
||||
config = set_item_in_config(config, key_path, value)
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def unset_config_value(config_path, key_path):
|
||||
def unset_config_value(config_path, key_path, validate=True):
|
||||
"""
|
||||
Unset key at key_path in config_path
|
||||
"""
|
||||
# FIXME: Have a file lock here
|
||||
# FIXME: Validate schema here
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config = yaml.load(f)
|
||||
except FileNotFoundError:
|
||||
config = {}
|
||||
with config_file_lock(config_path):
|
||||
config = get_current_config(config_path)
|
||||
config = unset_item_from_config(config, key_path)
|
||||
validate_config(config, validate)
|
||||
|
||||
config = unset_item_from_config(config, key_path)
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def add_config_value(config_path, key_path, value):
|
||||
def add_config_value(config_path, key_path, value, validate=True):
|
||||
"""
|
||||
Add value to list at key_path
|
||||
"""
|
||||
# FIXME: Have a file lock here
|
||||
# FIXME: Validate schema here
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config = yaml.load(f)
|
||||
except FileNotFoundError:
|
||||
config = {}
|
||||
with config_file_lock(config_path):
|
||||
config = get_current_config(config_path)
|
||||
config = add_item_to_config(config, key_path, value)
|
||||
validate_config(config, validate)
|
||||
|
||||
config = add_item_to_config(config, key_path, value)
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def remove_config_value(config_path, key_path, value):
|
||||
def remove_config_value(config_path, key_path, value, validate=True):
|
||||
"""
|
||||
Remove value from list at key_path
|
||||
"""
|
||||
# FIXME: Have a file lock here
|
||||
# FIXME: Validate schema here
|
||||
with config_file_lock(config_path):
|
||||
config = get_current_config(config_path)
|
||||
config = remove_item_from_config(config, key_path, value)
|
||||
validate_config(config, validate)
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
|
||||
|
||||
def get_current_config(config_path):
|
||||
"""
|
||||
Retrieve the current config at config_path
|
||||
"""
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config = yaml.load(f)
|
||||
return yaml.load(f)
|
||||
except FileNotFoundError:
|
||||
config = {}
|
||||
|
||||
config = remove_item_from_config(config, key_path, value)
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(config, f)
|
||||
return {}
|
||||
|
||||
|
||||
def check_hub_ready():
|
||||
"""
|
||||
Checks that hub is running.
|
||||
"""
|
||||
from .configurer import load_config
|
||||
|
||||
base_url = load_config()["base_url"]
|
||||
@@ -336,6 +364,18 @@ def main(argv=None):
|
||||
argparser.add_argument(
|
||||
"--config-path", default=CONFIG_FILE, help="Path to TLJH config.yaml file"
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--validate", action="store_true", help="Validate the TLJH config"
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--no-validate",
|
||||
dest="validate",
|
||||
action="store_false",
|
||||
help="Do not validate the TLJH config",
|
||||
)
|
||||
argparser.set_defaults(validate=True)
|
||||
|
||||
subparsers = argparser.add_subparsers(dest="action")
|
||||
|
||||
show_parser = subparsers.add_parser("show", help="Show current configuration")
|
||||
@@ -383,13 +423,19 @@ def main(argv=None):
|
||||
if args.action == "show":
|
||||
show_config(args.config_path)
|
||||
elif args.action == "set":
|
||||
set_config_value(args.config_path, args.key_path, parse_value(args.value))
|
||||
set_config_value(
|
||||
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||
)
|
||||
elif args.action == "unset":
|
||||
unset_config_value(args.config_path, args.key_path)
|
||||
unset_config_value(args.config_path, args.key_path, args.validate)
|
||||
elif args.action == "add-item":
|
||||
add_config_value(args.config_path, args.key_path, parse_value(args.value))
|
||||
add_config_value(
|
||||
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||
)
|
||||
elif args.action == "remove-item":
|
||||
remove_config_value(args.config_path, args.key_path, parse_value(args.value))
|
||||
remove_config_value(
|
||||
args.config_path, args.key_path, parse_value(args.value), args.validate
|
||||
)
|
||||
elif args.action == "reload":
|
||||
reload_component(args.component)
|
||||
else:
|
||||
|
||||
117
tljh/config_schema.py
Normal file
117
tljh/config_schema.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
The schema against which the TLJH config file can be validated.
|
||||
|
||||
Validation occurs when changing values with tljh-config.
|
||||
"""
|
||||
|
||||
config_schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Littlest JupyterHub YAML config file",
|
||||
"definitions": {
|
||||
"BaseURL": {
|
||||
"type": "string",
|
||||
},
|
||||
"Users": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"extra_user_groups": {"type": "object", "items": {"type": "string"}},
|
||||
"allowed": {"type": "array", "items": {"type": "string"}},
|
||||
"banned": {"type": "array", "items": {"type": "string"}},
|
||||
"admin": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
"Services": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cull": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean"},
|
||||
"timeout": {"type": "integer"},
|
||||
"every": {"type": "integer"},
|
||||
"concurrency": {"type": "integer"},
|
||||
"users": {"type": "boolean"},
|
||||
"max_age": {"type": "integer"},
|
||||
"remove_named_servers": {"type": "boolean"},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"HTTP": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"address": {"type": "string", "format": "ipv4"},
|
||||
"port": {"type": "integer"},
|
||||
},
|
||||
},
|
||||
"HTTPS": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean"},
|
||||
"address": {"type": "string", "format": "ipv4"},
|
||||
"port": {"type": "integer"},
|
||||
"tls": {"$ref": "#/definitions/TLS"},
|
||||
"letsencrypt": {"$ref": "#/definitions/LetsEncrypt"},
|
||||
},
|
||||
},
|
||||
"LetsEncrypt": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"email": {"type": "string", "format": "email"},
|
||||
"domains": {
|
||||
"type": "array",
|
||||
"items": {"type": "string", "format": "hostname"},
|
||||
},
|
||||
"staging": {"type": "boolean"},
|
||||
},
|
||||
},
|
||||
"TLS": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {"key": {"type": "string"}, "cert": {"type": "string"}},
|
||||
},
|
||||
"Limits": {
|
||||
"description": "User CPU and memory limits.",
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {"memory": {"type": "string"}, "cpu": {"type": "integer"}},
|
||||
},
|
||||
"UserEnvironment": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"default_app": {
|
||||
"type": "string",
|
||||
"enum": ["jupyterlab", "classic"],
|
||||
"default": "jupyterlab",
|
||||
}
|
||||
},
|
||||
},
|
||||
"TraefikAPI": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"ip": {"type": "string", "format": "ipv4"},
|
||||
"port": {"type": "integer"},
|
||||
"username": {"type": "string"},
|
||||
"password": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"properties": {
|
||||
"additionalProperties": False,
|
||||
"base_url": {"$ref": "#/definitions/BaseURL"},
|
||||
"user_environment": {"$ref": "#/definitions/UserEnvironment"},
|
||||
"users": {"$ref": "#/definitions/Users"},
|
||||
"limits": {"$ref": "#/definitions/Limits"},
|
||||
"https": {"$ref": "#/definitions/HTTPS"},
|
||||
"http": {"$ref": "#/definitions/HTTP"},
|
||||
"traefik_api": {"$ref": "#/definitions/TraefikAPI"},
|
||||
"services": {"$ref": "#/definitions/Services"},
|
||||
},
|
||||
}
|
||||
@@ -199,6 +199,14 @@ def update_userlists(c, config):
|
||||
"""
|
||||
users = config["users"]
|
||||
|
||||
if (
|
||||
not users["allowed"]
|
||||
and config["auth"]["type"] == default["auth"]["type"]
|
||||
and "allow_all" not in c.FirstUseAuthenticator
|
||||
):
|
||||
# _default_ authenticator, enable allow_all if no users specified
|
||||
c.FirstUseAuthenticator.allow_all = True
|
||||
|
||||
c.Authenticator.allowed_users = set(users["allowed"])
|
||||
c.Authenticator.blocked_users = set(users["banned"])
|
||||
c.Authenticator.admin_users = set(users["admin"])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Hook specifications that pluggy plugins can override
|
||||
"""
|
||||
|
||||
import pluggy
|
||||
|
||||
hookspec = pluggy.HookspecMarker("tljh")
|
||||
@@ -14,6 +15,13 @@ def tljh_extra_user_conda_packages():
|
||||
"""
|
||||
|
||||
|
||||
@hookspec
|
||||
def tljh_extra_user_conda_channels():
|
||||
"""
|
||||
Return a list of conda channels to be used during user environment installation.
|
||||
"""
|
||||
|
||||
|
||||
@hookspec
|
||||
def tljh_extra_user_pip_packages():
|
||||
"""
|
||||
|
||||
@@ -115,7 +115,6 @@ def ensure_jupyterhub_package(prefix):
|
||||
os.path.join(HERE, "requirements-hub-env.txt"),
|
||||
upgrade=True,
|
||||
)
|
||||
traefik.ensure_traefik_binary(prefix)
|
||||
|
||||
|
||||
def ensure_usergroups():
|
||||
@@ -136,13 +135,13 @@ def ensure_usergroups():
|
||||
f.write("Defaults exempt_group = jupyterhub-admins\n")
|
||||
|
||||
|
||||
# Install mambaforge using an installer from
|
||||
# Install miniforge using an installer from
|
||||
# https://github.com/conda-forge/miniforge/releases
|
||||
MAMBAFORGE_VERSION = "23.1.0-1"
|
||||
MINIFORGE_VERSION = "24.7.1-2"
|
||||
# sha256 checksums
|
||||
MAMBAFORGE_CHECKSUMS = {
|
||||
"aarch64": "d9d89c9e349369702171008d9ee7c5ce80ed420e5af60bd150a3db4bf674443a",
|
||||
"x86_64": "cfb16c47dc2d115c8b114280aa605e322173f029fdb847a45348bf4bd23c62ab",
|
||||
MINIFORGE_CHECKSUMS = {
|
||||
"aarch64": "7bf60bce50f57af7ea4500b45eeb401d9350011ab34c9c45f736647d8dba9021",
|
||||
"x86_64": "636f7faca2d51ee42b4640ce160c751a46d57621ef4bf14378704c87c5db4fe3",
|
||||
}
|
||||
|
||||
# minimum versions of packages
|
||||
@@ -156,22 +155,22 @@ MINIMUM_VERSIONS = {
|
||||
}
|
||||
|
||||
|
||||
def _mambaforge_url(version=MAMBAFORGE_VERSION, arch=None):
|
||||
"""Return (URL, checksum) for mambaforge download for a given version and arch
|
||||
def _miniforge_url(version=MINIFORGE_VERSION, arch=None):
|
||||
"""Return (URL, checksum) for miniforge download for a given version and arch
|
||||
|
||||
Default values provided for both version and arch
|
||||
"""
|
||||
if arch is None:
|
||||
arch = os.uname().machine
|
||||
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Mambaforge-{v}-Linux-{arch}.sh".format(
|
||||
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Miniforge3-{v}-Linux-{arch}.sh".format(
|
||||
v=version,
|
||||
arch=arch,
|
||||
)
|
||||
# Check system architecture, set appropriate installer checksum
|
||||
checksum = MAMBAFORGE_CHECKSUMS.get(arch)
|
||||
checksum = MINIFORGE_CHECKSUMS.get(arch)
|
||||
if not checksum:
|
||||
raise ValueError(
|
||||
f"Unsupported architecture: {arch}. TLJH only supports {','.join(MAMBAFORGE_CHECKSUMS.keys())}"
|
||||
f"Unsupported architecture: {arch}. TLJH only supports {','.join(MINIFORGE_CHECKSUMS.keys())}"
|
||||
)
|
||||
return installer_url, checksum
|
||||
|
||||
@@ -198,7 +197,7 @@ def ensure_user_environment(user_requirements_txt_file):
|
||||
raise OSError(msg)
|
||||
|
||||
logger.info("Downloading & setting up user environment...")
|
||||
installer_url, installer_sha256 = _mambaforge_url()
|
||||
installer_url, installer_sha256 = _miniforge_url()
|
||||
with conda.download_miniconda_installer(
|
||||
installer_url, installer_sha256
|
||||
) as installer_path:
|
||||
@@ -242,11 +241,10 @@ def ensure_user_environment(user_requirements_txt_file):
|
||||
)
|
||||
to_upgrade.append(pkg)
|
||||
|
||||
# force reinstall conda/mamba to ensure a basically consistent env
|
||||
# avoids issues with RemoveError: 'requests' is a dependency of conda
|
||||
# only do this for 'old' conda versions known to have a problem
|
||||
# we don't know how old, but we know 4.10 is affected and 23.1 is not
|
||||
if not is_fresh_install and V(package_versions.get("conda", "0")) < V("23.1"):
|
||||
# force reinstall conda/mamba to ensure conda doesn't raise error
|
||||
# "RemoveError: 'requests' is a dependency of conda" later on when
|
||||
# conda/mamba is used to install/upgrade something
|
||||
if not is_fresh_install:
|
||||
# force-reinstall doesn't upgrade packages
|
||||
# it reinstalls them in-place
|
||||
# only reinstall packages already present
|
||||
@@ -450,13 +448,18 @@ def run_plugin_actions(plugin_manager):
|
||||
|
||||
# Install conda packages
|
||||
conda_packages = list(set(itertools.chain(*hook.tljh_extra_user_conda_packages())))
|
||||
conda_channels = list(itertools.chain(*hook.tljh_extra_user_conda_channels()))
|
||||
if len(conda_channels) == 0:
|
||||
conda_channels = ("conda-forge",)
|
||||
if conda_packages:
|
||||
logger.info(
|
||||
"Installing {} user conda packages collected from plugins: {}".format(
|
||||
len(conda_packages), " ".join(conda_packages)
|
||||
)
|
||||
)
|
||||
conda.ensure_conda_packages(USER_ENV_PREFIX, conda_packages)
|
||||
conda.ensure_conda_packages(
|
||||
USER_ENV_PREFIX, conda_packages, channels=conda_channels
|
||||
)
|
||||
|
||||
# Install pip packages
|
||||
user_pip_packages = list(set(itertools.chain(*hook.tljh_extra_user_pip_packages())))
|
||||
@@ -532,6 +535,7 @@ def main():
|
||||
|
||||
logger.info("Setting up JupyterHub...")
|
||||
ensure_jupyterhub_package(HUB_ENV_PREFIX)
|
||||
traefik.ensure_traefik_binary(HUB_ENV_PREFIX)
|
||||
|
||||
# Stop the http server with the progress page before traefik starts
|
||||
if args.progress_page_server_pid:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Setup tljh logging"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Functions to normalize various inputs
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
# If a dependency is bumped to a new major version, we should make a major
|
||||
# version release of tljh.
|
||||
#
|
||||
jupyterhub>=4.0.2,<5
|
||||
jupyterhub>=5.1.0,<6
|
||||
jupyterhub-systemdspawner>=1.0.1,<2
|
||||
jupyterhub-firstuseauthenticator>=1.0.0,<2
|
||||
jupyterhub-nativeauthenticator>=1.2.0,<2
|
||||
jupyterhub-ldapauthenticator>=1.3.2,<2
|
||||
jupyterhub-firstuseauthenticator>=1.1.0,<2
|
||||
jupyterhub-nativeauthenticator>=1.3.0,<2
|
||||
jupyterhub-ldapauthenticator==2.0.0b2 # FIXME: update to >=2.0.0,<3
|
||||
jupyterhub-tmpauthenticator>=1.0.0,<2
|
||||
oauthenticator>=16.0.4,<17
|
||||
jupyterhub-idle-culler>=1.2.1,<2
|
||||
oauthenticator>=17,<18
|
||||
jupyterhub-idle-culler>=1.4.0,<2
|
||||
|
||||
# pycurl is installed to improve reliability and performance for when JupyterHub
|
||||
# makes web requests. JupyterHub will use tornado's CurlAsyncHTTPClient when
|
||||
@@ -25,4 +25,4 @@ jupyterhub-idle-culler>=1.2.1,<2
|
||||
# ref: https://www.tornadoweb.org/en/stable/httpclient.html#module-tornado.simple_httpclient
|
||||
# ref: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
|
||||
#
|
||||
pycurl>=7.45.2,<8
|
||||
pycurl>=7.45.3,<8
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
# the requirements-txt-fixer pre-commit hook that sorted them and made
|
||||
# our integration tests fail.
|
||||
#
|
||||
notebook==7.*
|
||||
jupyterlab==4.*
|
||||
# ref: https://github.com/jupyter/notebook
|
||||
notebook>=7.2.2,<8
|
||||
|
||||
# ref: https://github.com/jupyterlab/jupyterlab
|
||||
jupyterlab>=4.2.5,<5
|
||||
|
||||
# nbgitpuller for easily pulling in Git repositories
|
||||
nbgitpuller==1.*
|
||||
# ref: https://github.com/jupyterhub/nbgitpuller
|
||||
nbgitpuller>=1.2.1,<2
|
||||
|
||||
# jupyter-resource-usage to show people how much RAM they are using
|
||||
jupyter-resource-usage==1.*
|
||||
# ref: https://github.com/jupyter-server/jupyter-resource-usage
|
||||
jupyter-resource-usage>=1.1.0,<2
|
||||
|
||||
# Most people consider ipywidgets to be part of the core notebook experience
|
||||
ipywidgets==8.*
|
||||
# ref: https://github.com/jupyter-widgets/ipywidgets
|
||||
ipywidgets>=8.1.5,<9
|
||||
|
||||
@@ -18,7 +18,7 @@ Environment=TLJH_INSTALL_PREFIX={install_prefix}
|
||||
Environment=PATH={install_prefix}/hub/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# Run upgrade-db before starting, in case Hub version has changed
|
||||
# This is a no-op when no db exists or no upgrades are needed
|
||||
ExecStart={python_interpreter_path} -m jupyterhub.app -f {jupyterhub_config_path} --upgrade-db
|
||||
ExecStart={python_interpreter_path} -m jupyterhub -f {jupyterhub_config_path} --upgrade-db
|
||||
|
||||
[Install]
|
||||
# Start service when system boots
|
||||
|
||||
@@ -3,6 +3,7 @@ Wraps systemctl to install, uninstall, start & stop systemd services.
|
||||
|
||||
If we use a debian package instead, we can get rid of all this code.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Traefik installation and setup"""
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import logging
|
||||
@@ -28,13 +29,13 @@ else:
|
||||
plat = None
|
||||
|
||||
# Traefik releases: https://github.com/traefik/traefik/releases
|
||||
traefik_version = "2.10.1"
|
||||
traefik_version = "3.1.4"
|
||||
|
||||
# record sha256 hashes for supported platforms here
|
||||
# checksums are published in the checksums.txt of each release
|
||||
checksums = {
|
||||
"linux_amd64": "8d9bce0e6a5bf40b5399dbb1d5e3e5c57b9f9f04dd56a2dd57cb0713130bc824",
|
||||
"linux_arm64": "260a574105e44901f8c9c562055936d81fbd9c96a21daaa575502dc69bfe390a",
|
||||
"linux_amd64": "eb7227b1b235195355904839c514a9ed6a0aecdcf5dab02ad48db21b05c5e700",
|
||||
"linux_arm64": "e5d970a7f11267b70a8e308cb80f859bba4f420f24789f7393fdf3f4cd031631",
|
||||
}
|
||||
|
||||
_tljh_path = Path(__file__).parent.resolve()
|
||||
@@ -90,7 +91,10 @@ def check_traefik_version(traefik_bin):
|
||||
|
||||
@backoff.on_exception(backoff.expo, Exception, max_tries=2, giveup=fatal_error)
|
||||
def ensure_traefik_binary(prefix):
|
||||
"""Download and install the traefik binary to a location identified by a prefix path such as '/opt/tljh/hub/'"""
|
||||
"""
|
||||
Ensure that a traefik binary of a hardcoded version is made available at a
|
||||
prefix path such as '/opt/tljh/hub/'.
|
||||
"""
|
||||
if plat is None:
|
||||
raise OSError(
|
||||
f"Error. Platform: {os.uname().sysname} / {machine} Not supported."
|
||||
|
||||
@@ -3,6 +3,7 @@ User management for tljh.
|
||||
|
||||
Supports minimal user & group management
|
||||
"""
|
||||
|
||||
import grp
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Miscellaneous functions useful in at least two places unrelated to each other
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
ensures the same yaml settings for reading/writing
|
||||
throughout tljh
|
||||
"""
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
from ruamel.yaml.composer import Composer
|
||||
|
||||
|
||||
Reference in New Issue
Block a user