diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..28f1c9b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# dependabot.yml 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 +# https://github.com/jupyterhub/the-littlest-jupyterhub/network/updates. +# - YAML anchors are not supported here or in GitHub Workflows. +# +version: 2 +updates: + # Maintain dependencies in our GitHub Workflows + - package-ecosystem: github-actions + directory: "/" # This should be / rather than .github/workflows + schedule: + interval: weekly + time: "05:00" + timezone: "Etc/UTC" diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 6babbc1..a0cf9fa 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -66,22 +66,21 @@ jobs: ubuntu_version: "20.04" python_version: "3.9" extra_flags: "" - - name: "Int. tests: Ubuntu 21.10, Py 3.9" - runs_on: "20.04" - ubuntu_version: "21.10" - python_version: "3.9" + - name: "Int. tests: Ubuntu 22.04, Py 3.10" + ubuntu_version: "22.04" + python_version: "3.10" extra_flags: "" - - name: "Int. tests: Ubuntu 20.04, Py 3.9, --upgrade" - ubuntu_version: "20.04" - python_version: "3.9" + - name: "Int. tests: Ubuntu 22.04, Py 3.10, --upgrade" + ubuntu_version: "22.04" + python_version: "3.10" extra_flags: --upgrade dont_run_on_ref: refs/heads/master integration-tests: needs: decide-on-test-jobs-to-run - # runs-on can only be configured to the LTS releases of ubuntu (18.04, - # 20.04, ...), so if we want to test against the latest non-LTS release, we + # runs-on can only be configured to the LTS releases of ubuntu (20.04, + # 22.04, ...), so if we want to test against the latest non-LTS release, we # must compromise when configuring runs-on and configure runs-on to be the # latest LTS release instead. # @@ -96,10 +95,10 @@ jobs: matrix: ${{ fromJson(needs.decide-on-test-jobs-to-run.outputs.matrix) }} steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python_version }} + python-version: "${{ matrix.python_version }}" - name: Install pytest run: python3 -m pip install pytest @@ -148,7 +147,8 @@ jobs: # GitHub branch for example. - name: Set BOOTSTRAP_PIP_SPEC value run: | - echo "BOOTSTRAP_PIP_SPEC=git+https://github.com/$GITHUB_REPOSITORY.git@$GITHUB_REF" >> $GITHUB_ENV + BOOTSTRAP_PIP_SPEC="git+https://github.com/$GITHUB_REPOSITORY.git@$GITHUB_REF" + echo "BOOTSTRAP_PIP_SPEC=$BOOTSTRAP_PIP_SPEC" >> $GITHUB_ENV echo $BOOTSTRAP_PIP_SPEC - name: Run basic tests (Runs in ubuntu:${{ matrix.ubuntu_version }} derived image) diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index 5de90ce..71804f3 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -48,19 +48,15 @@ jobs: - name: "Unit tests: Ubuntu 20.04, Py 3.9" ubuntu_version: "20.04" python_version: "3.9" - # Test against Ubuntu 21.10 fails as of 2021-10-18 fail with the error - # described in: https://github.com/jupyterhub/the-littlest-jupyterhub/issues/714#issuecomment-945154101 - # - # - name: "Unit tests: Ubuntu 21.10, Py 3.9" - # runs_on: "20.04" - # ubuntu_version: "21.10" - # python_version: "3.9" + - name: "Unit tests: Ubuntu 22.04, Py 3.10" + ubuntu_version: "22.04" + python_version: "3.10" steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python_version }} + python-version: "${{ matrix.python_version }}" - name: Install venv, git and setup venv run: | @@ -78,7 +74,7 @@ jobs: # 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@v2 + uses: actions/cache@v3 with: path: /srv/venv/ key: >- diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a075ff9..e63b7cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: # Autoformat: Python code, syntax patterns are modernized - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v2.34.0 hooks: - id: pyupgrade args: @@ -22,19 +22,19 @@ repos: # Autoformat: Python code - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 22.3.0 hooks: - id: black # Autoformat: markdown, yaml - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.4.1 + rev: v2.7.1 hooks: - id: prettier # Misc... - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.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. diff --git a/bootstrap/bootstrap.py b/bootstrap/bootstrap.py index 14b6fce..a34df09 100644 --- a/bootstrap/bootstrap.py +++ b/bootstrap/bootstrap.py @@ -38,16 +38,18 @@ Command line flags: passed, it will pass --progress-page-server-pid= to the tljh installer for later termination. """ +from argparse import ArgumentParser import os from http.server import SimpleHTTPRequestHandler, HTTPServer import multiprocessing +import re import subprocess import sys import logging import shutil import urllib.request -progress_page_favicon_url = "https://raw.githubusercontent.com/jupyterhub/jupyterhub/HEAD/share/jupyterhub/static/favicon.ico" +progress_page_favicon_url = "https://raw.githubusercontent.com/jupyterhub/jupyterhub/main/share/jupyterhub/static/favicon.ico" progress_page_html = """ @@ -164,9 +166,11 @@ def run_subprocess(cmd, *args, **kwargs): command=printable_command, code=proc.returncode ) ) + output = proc.stdout.decode() # This produces multi line log output, unfortunately. Not sure how to fix. # For now, prioritizing human readability over machine readability. - logger.debug(proc.stdout.decode()) + logger.debug(output) + return output def ensure_host_system_can_install_tljh(): @@ -253,10 +257,77 @@ class ProgressPageRequestHandler(SimpleHTTPRequestHandler): SimpleHTTPRequestHandler.send_error(self, code=403) +def _find_matching_version(all_versions, requested): + """ + Find the latest version that is less than or equal to requested. + all_versions must be int-tuples. + requested must be an int-tuple or "latest" + + Returns None if no version is found. + """ + sorted_versions = sorted(all_versions, reverse=True) + if requested == "latest": + return sorted_versions[0] + components = len(requested) + for v in sorted_versions: + if v[:components] == requested: + return v + return None + + +def _resolve_git_version(version): + """ + Resolve the version argument to a git ref using git ls-remote + - If version looks like MAJOR.MINOR.PATCH or a partial tag then fetch all tags + and return the most latest tag matching MAJOR.MINOR.PATCH + (e.g. version=0.1 -> 0.1.PATCH). This should ignore dev tags + - If version='latest' then return the latest release tag + - Otherwise assume version is a branch or hash and return it without checking + """ + + if version != "latest" and not re.match(r"\d+(\.\d+)?(\.\d+)?$", version): + return version + + all_versions = set() + out = run_subprocess( + [ + "git", + "ls-remote", + "--tags", + "--refs", + "https://github.com/jupyterhub/the-littlest-jupyterhub.git", + ] + ) + + for line in out.splitlines(): + m = re.match(r"(?P[a-f0-9]+)\s+refs/tags/(?P[\S]+)$", line) + if not m: + raise Exception("Unexpected git ls-remote output: {}".format(line)) + tag = m.group("tag") + if tag == version: + return tag + if re.match(r"\d+\.\d+\.\d+$", tag): + all_versions.add(tuple(int(v) for v in tag.split("."))) + + if not all_versions: + raise Exception("No MAJOR.MINOR.PATCH git tags found") + + if version == "latest": + requested = "latest" + else: + requested = tuple(int(v) for v in version.split(".")) + found = _find_matching_version(all_versions, requested) + if not found: + raise Exception( + "No version matching {} found {}".format(version, sorted(all_versions)) + ) + return ".".join(str(f) for f in found) + + def main(): """ - This script intercepts the --show-progress-page flag, but all other flags - are passed through to the TLJH installer script. + This bootstrap script intercepts some command line flags, everything else is + passed through to the TLJH installer script. The --show-progress-page flag indicates that the bootstrap script should start a local webserver temporarily and report its installation progress via @@ -264,6 +335,13 @@ def main(): """ distro, version = ensure_host_system_can_install_tljh() + parser = ArgumentParser() + parser.add_argument("--show-progress-page", action="store_true") + parser.add_argument( + "--version", default="main", help="TLJH version or Git reference" + ) + args, tljh_installer_flags = parser.parse_known_args() + # Various related constants install_prefix = os.environ.get("TLJH_INSTALL_PREFIX", "/opt/tljh") hub_prefix = os.path.join(install_prefix, "hub") @@ -273,12 +351,7 @@ def main(): # Attempt to start a web server to serve a progress page reporting # installation progress. - tljh_installer_flags = sys.argv[1:] - if "--show-progress-page" in tljh_installer_flags: - # Remove the bootstrap specific flag and let all other flags pass - # through to the installer. - tljh_installer_flags.remove("--show-progress-page") - + if args.show_progress_page: # Write HTML and a favicon to be served by our webserver with open("/var/run/index.html", "w+") as f: f.write(progress_page_html) @@ -380,10 +453,14 @@ def main(): if os.environ.get("TLJH_BOOTSTRAP_DEV", "no") == "yes": logger.info("Selected TLJH_BOOTSTRAP_DEV=yes...") tljh_install_cmd.append("--editable") + version = _resolve_git_version(args.version) + tljh_install_cmd.append( os.environ.get( "TLJH_BOOTSTRAP_PIP_SPEC", - "git+https://github.com/jupyterhub/the-littlest-jupyterhub.git", + "git+https://github.com/jupyterhub/the-littlest-jupyterhub.git@{}".format( + version + ), ) ) if initial_setup: diff --git a/docs/howto/content/nbgitpuller.rst b/docs/howto/content/nbgitpuller.rst index 152fd55..5daf7b9 100644 --- a/docs/howto/content/nbgitpuller.rst +++ b/docs/howto/content/nbgitpuller.rst @@ -24,7 +24,7 @@ Instructors should be able to: 1. Use modern collaborative version control tools to author & store their materials. This currently means using Git. -**nbgitpuller** is a Jupyter Notebook extension that helps achieve these goals. +**nbgitpuller** is a Jupyter server extension that helps achieve these goals. This tutorial will walk you through the process of creating a magic nbgitpuller link that users of your JupyterHub can click to fetch the latest version of materials from a git repo. @@ -38,76 +38,10 @@ Pre-requisites Step 1: Generate nbgitpuller link ================================= -**Generate the link with a Binder app**. - -#. The easiest way to generate an nbgitpuller link is to use the - `mybinder.org based application `_. - Open it, and wait for it to load. - - .. image:: ../../images/nbgitpuller/binder-progress.png - :alt: Progress bar as the binder application loads - -#. A blank form with some help text will open up. - - .. image:: ../../images/nbgitpuller/blank-application.png - :alt: Blank application to make nbgitpuller links - -#. Enter the IP address or URL to your JupyterHub under ``hub_url``. - Include ``http://`` or ``https://`` as appropriate. - - .. image:: ../../images/nbgitpuller/hub-url-application.png - :alt: Application with hub_url filled out - -#. Enter the URL to your Git repository. This could be from GitHub, - GitLab or any other git provider - including the disk of the - server The Littlest JupyterHub is installed on. As you start - typing the URL here, you'll notice that the link is already - being printed below! - - .. image:: ../../images/nbgitpuller/git-url-application.png - :alt: Application with git_url filled out - -#. If your git repository is using a non-default branch name, - you can specify that under ``branch``. Most people do not - need to customize this. - -#. If you want to open a specific notebook when the user clicks - on the link, specify the path to the notebook under ``filepath``. - Make sure this file exists, otherwise users will get a 'File not found' - error. - - .. image:: ../../images/nbgitpuller/filepath-application.png - :alt: Application with filepath filled out - - If you do not specify a file path, the user will be shown the - directory listing for the repository. - -#. By default, notebooks will be opened in the classic Jupyter Notebook - interface. You can select ``lab`` under ``application`` to open it in the - `JupyterLab `_ instead. - -The link printed at the bottom of the form can be distributed to students -now! You can also click it to test that it is working as intended, -and adjust the form values until you get something you are happy with. - -**Hand-craft your nbgitpuller link** - -If you'd prefer to hand-craft your ``nbgitpuller`` link (e.g. if the Binder -link above doesn't work), you can use the following pattern:: - - http:///hub/user-redirect/git-pull?repo=&branch=&subPath=&app= - -- **repo** is the URL of the git repository you want to clone. This parameter is required. -- **branch** is the branch name to use when cloning from the repository. - This parameter is optional and defaults to ``master``. -- **subPath** is the path of the directory / notebook inside the repo to launch after cloning. - This parameter is optional, and defaults to opening the base directory of the linked Git repository. -- **app** This parameter is optional and defaults to either the environment variable - `NBGITPULLER_APP`'s value or `notebook` if it is undefined. The allowed values - are `lab` and `notebook`, the value will determine in what application view - you end up in. -- **urlPath** will, if specified, override `app` and `subPath` and redirect - blindly to the specified path. +The quickest way to generate a link is to use `nbgitpuller.link +`_, but other options exist as described in the +`nbgitpuller project's documentation +`_. Step 2: Users click on the nbgitpuller link =========================================== @@ -130,9 +64,3 @@ Step 2: Users click on the nbgitpuller link This workflow lets users land directly in the notebook you specified without having to understand much about git or the JupyterHub interface. - -Advanced: hand-crafting an nbgitpuller link -=========================================== - -For information on hand-crafting an ``nbgitpuller`` link, see -`the nbgitpuller README `_. diff --git a/docs/images/nbgitpuller/binder-progress.png b/docs/images/nbgitpuller/binder-progress.png deleted file mode 100644 index bc5045b..0000000 Binary files a/docs/images/nbgitpuller/binder-progress.png and /dev/null differ diff --git a/docs/images/nbgitpuller/blank-application.png b/docs/images/nbgitpuller/blank-application.png deleted file mode 100644 index a02f87c..0000000 Binary files a/docs/images/nbgitpuller/blank-application.png and /dev/null differ diff --git a/docs/images/nbgitpuller/filepath-application.png b/docs/images/nbgitpuller/filepath-application.png deleted file mode 100644 index 2871ce5..0000000 Binary files a/docs/images/nbgitpuller/filepath-application.png and /dev/null differ diff --git a/docs/images/nbgitpuller/git-url-application.png b/docs/images/nbgitpuller/git-url-application.png deleted file mode 100644 index d1bc1f3..0000000 Binary files a/docs/images/nbgitpuller/git-url-application.png and /dev/null differ diff --git a/docs/images/nbgitpuller/hub-url-application.png b/docs/images/nbgitpuller/hub-url-application.png deleted file mode 100644 index f3d5930..0000000 Binary files a/docs/images/nbgitpuller/hub-url-application.png and /dev/null differ diff --git a/docs/topic/idle-culler.rst b/docs/topic/idle-culler.rst index b8c520b..21e3889 100644 --- a/docs/topic/idle-culler.rst +++ b/docs/topic/idle-culler.rst @@ -8,13 +8,14 @@ The idle culler automatically shuts down user notebook servers when they have not been used for a certain time period, in order to reduce the total resource usage on your JupyterHub. -JupyterHub pings the user's notebook server at certain time intervals. If no response -is received from the server during this checks and the timeout expires, the server is -considered to be *inactive (idle)* and will be culled. +The notebook server monitors activity internally +and notifies JupyterHub of recent activity at certain time intervals (the activity interval). +If JupyterHub has not been notified of any activity after a certain period (the idle timeout), +the server is considered to be *inactive (idle)* and will be culled (shutdown). The `idle culler `_ is a JupyterHub service that is installed and enabled by default in TLJH. It can be configured using tljh-config. For advanced use-cases, like purging old user data, -the idle culler configuration can be extended beyond tljh-config options, using custom +the idle culler configuration can be extended beyond tljh-config options, using custom `jupyterhub_config.py snippets `__. diff --git a/tests/test_bootstrap_functions.py b/tests/test_bootstrap_functions.py new file mode 100644 index 0000000..c579ac8 --- /dev/null +++ b/tests/test_bootstrap_functions.py @@ -0,0 +1,128 @@ +# Unit test some functions from bootstrap.py +# Since bootstrap.py isn't part of the package, it's not automatically importable +import os +import sys + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from bootstrap import bootstrap +import pytest + + +@pytest.mark.parametrize( + "requested,expected", + [ + ((1,), (1, 1, 0)), + ((1, 0), (1, 0, 1)), + ((1, 2), None), + ((2, 0, 0), (2, 0, 0)), + ("latest", (2, 0, 1)), + ], +) +def test_find_matching_version(requested, expected): + all_versions = [ + (0, 0, 1), + (1, 0, 0), + (1, 0, 1), + (1, 1, 0), + (2, 0, 0), + (2, 0, 1), + ] + r = bootstrap._find_matching_version(all_versions, requested) + assert r == expected + + +# git ls-remote --tags --refs https://github.com/jupyterhub/jupyterhub.git +mock_git_ls_remote = """\ +c345aa658eb482b8102b51f6ec3f0fc667b60520 refs/tags/0.1.0 +e2cbc2fb41c04cfe416b19e83f36ea67b1a4e693 refs/tags/0.2.0 +7fd56fe943bf0038cb14e7aa2af5a3e5ad929d47 refs/tags/0.3.0 +89cf4c60a905b4093ba7584c4561073a9faa0d3d refs/tags/0.4.0 +bd08efc4a5485a9ecd17882e9bfcab9486d9956a refs/tags/0.4.1 +19c77d0c7016c08a4a0e883446114a430a551882 refs/tags/0.5.0 +b4621d354b6bbc865373b7c033f29c6872237780 refs/tags/0.6.0 +ed38f08daf4d5cf84f04b2d96327681221c579dd refs/tags/0.6.1 +d5cf9657f2ca16df080e2be21da792288e9f4f99 refs/tags/0.7.0 +2cdb4be46a0eb291850fc706cfe044873889a9bc refs/tags/0.7.1 +e4dd65d2611e3c85fe486c561fc0efe9ca720042 refs/tags/0.7.2 +4179668e49ceedb805cb1a38dc5a70e6a21fa685 refs/tags/0.8.0 +9bab206eb96c6726ac936cf5da3f61eb9c9aa519 refs/tags/0.8.0b1 +5f2c6d25fefcbe91d86945f530e6533822449c46 refs/tags/0.8.0b2 +0d720103c5207d008947580b7b453e1eb0e7594a refs/tags/0.8.0b3 +a2458ffa402fa2d2905c558100c673e98789a8a8 refs/tags/0.8.0b4 +b9e2838a4d9b35a9ad7c3353e62ab006b4ec10a4 refs/tags/0.8.0b5 +a62fc1bc1c9b2408713511cb56e7751403ed5503 refs/tags/0.8.0rc1 +a77ca08e3e25c14552e246e8ad3ca65a354ba192 refs/tags/0.8.0rc2 +6eef64842a7d7939b3f1986558849d1977a0e121 refs/tags/0.8.1 +de46a16029b7ae217293e7e64e14a9c2e06e5e60 refs/tags/0.9.0 +9f612b52187db878f529458e304bd519fda82e42 refs/tags/0.9.0b1 +ec4b038b93495eb769007a0d3d56e6d6a5ff000c refs/tags/0.9.0b2 +ea8a30b1a5b189b2f2f0dbfdb22f83427d1c9163 refs/tags/0.9.0b3 +99c155a61a1d95a3a8ca41ebb684cdedc1fb170f refs/tags/0.9.0rc1 +1aeebd4e4937ea5185ce02f693f94272c30f4ebd refs/tags/0.9.1 +01b3601a12b52259b541b48eaa7a7afb3f7d988c refs/tags/0.9.2 +70ddc22e071bb7797528831d25c567f6f4920c67 refs/tags/0.9.3 +7ecb093163a453ae2edfa6bc8bf1f7cfc2320627 refs/tags/0.9.4 +3e83bc440b8d5abdc0a4336978bd542435402f77 refs/tags/0.9.5 +cc07c706931c78f46367db9c0c20e6ed9f0f6f85 refs/tags/0.9.6 +4e24276d40ad83fd113c7c2f1f8619a9ba3af0d8 refs/tags/1.0.0 +582162c760e81995f4f5405e9c8908d2a76f4abf refs/tags/1.0.0b1 +1193f6a74c38b36594f6f57c786fa923a2747150 refs/tags/1.0.0b2 +512dae6cd8a846dd490d77d21fd4e13f59c38961 refs/tags/1.1.0 +a420c55391273852332ef5f454a0a3b9e0e5b71f refs/tags/1.1.0b1 +317f0efaf25eb7cb2de4503817cf20937ce110bd refs/tags/1.2.0 +f66e5d35b5f89a28f6328c91801a8f99e0855a8e refs/tags/1.2.0b1 +27e1196471729cf6f25fd3786286797e32de973a refs/tags/1.2.1 +af0c1ed932d00fa26ac91f066a5a9eafb49b7cb1 refs/tags/1.2.2 +3794abfbdda0a92237f4c31985420691da70da36 refs/tags/1.3.0 +e22ab5dc93dd8e724b828a0880032f6b5dc00231 refs/tags/1.4.0 +0656586b75b30091583c0573b3d272cb3add24d2 refs/tags/1.4.1 +5744ce73bcf0014cc3de6c946f12027448b136da refs/tags/1.4.2 +c6fb64d8f30686c2c2667b69b53402d506a3bac5 refs/tags/1.5.0 +4ceb906435dbd4cf800b0480d413303f056e4900 refs/tags/2.0.0 +61233698dfb353c703ea2e085312b9066ea2e92e refs/tags/2.0.0b1 +fe61c932409550dc352abf68bd6aaaa8871ac81f refs/tags/2.0.0b2 +a79c5c5a6bfe553af277f2835419d65b98ae0cb9 refs/tags/2.0.0b3 +fa1098a998561321de29c6147235032fd6b0c3f5 refs/tags/2.0.0rc1 +75b115c356983c138c2d8d92cb45f068ad3d9c9d refs/tags/2.0.0rc2 +ed8e25ef3f471d60b671f2a1cf2db17581c778a2 refs/tags/2.0.0rc3 +4083307b3f37039075862034963ed42a459b1bdb refs/tags/2.0.0rc4 +baf1f36dbfe8d8264b3914650b4db6daed843389 refs/tags/2.0.0rc5 +12961be3b13a10617d8f95f333da2bb67390a2c7 refs/tags/2.0.1 +e40df3f1a5e284926f5c9ce66a1e57a814bb98f8 refs/tags/2.0.2 +11d40c13860bd02816ad724979ad2e08b8bd103a refs/tags/2.1.0 +bde6b66287e3d157f2577bcaf2e986af020139f4 refs/tags/2.1.1 +29f51794db562ecc4c7653525193d6e210151fdb refs/tags/2.2.0 +8cbafd25425b7eaf2fdc46e183cee437c09b53c1 refs/tags/2.2.1 +1eada986101f2385ee7498395a799f28bcd167e8 refs/tags/2.2.2 +1bb0ec38ae4c5e4e5c8b6cc3b89b7b20ea8bd400 refs/tags/2.3.0 +69f926706be03505f9b9e30a5ad2d4f8c9f9d48d refs/tags/2.3.1 +""" + + +@pytest.mark.parametrize( + "requested,expected", + [ + ("1", "1.5.0"), + ("1.0", "1.0.0"), + ("1.2", "1.2.2"), + ("1.100", None), + ("2.0.0", "2.0.0"), + ("random-branch", "random-branch"), + ("1234567890abcdef", "1234567890abcdef"), + ("2.0.0rc4", "2.0.0rc4"), + ("latest", "2.3.1"), + ], +) +def test_resolve_git_version(monkeypatch, requested, expected): + def mock_run_subprocess(*args, **kwargs): + return mock_git_ls_remote + + monkeypatch.setattr(bootstrap, "run_subprocess", mock_run_subprocess) + + if expected is None: + with pytest.raises(Exception) as exc: + bootstrap._resolve_git_version(requested) + assert exc.value.args[0].startswith("No version matching 1.100 found") + else: + assert bootstrap._resolve_git_version(requested) == expected diff --git a/tljh/installer.py b/tljh/installer.py index 6852c5c..42ba070 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -122,7 +122,7 @@ def ensure_jupyterhub_package(prefix): prefix, [ "jupyterhub==1.*", - "jupyterhub-systemdspawner==0.15.*", + "jupyterhub-systemdspawner==0.16.*", "jupyterhub-firstuseauthenticator==1.*", "jupyterhub-nativeauthenticator==1.*", "jupyterhub-ldapauthenticator==1.*", @@ -447,7 +447,7 @@ def main(): ensure_admins(args.admin) ensure_usergroups() if args.user_requirements_txt_url: - logger.info("installing packages from user_requirements_txt_url") + logger.info("installing packages from user_requirements_txt_url") ensure_user_environment(args.user_requirements_txt_url) logger.info("Setting up JupyterHub...")