diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 0e53be4f..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Python CircleCI 2.0 configuration file -# Updating CircleCI configuration from v1 to v2 -# Check https://circleci.com/docs/2.0/language-python/ for more details -# -version: 2 -jobs: - build: - machine: true - steps: - - checkout - - run: - name: build images - command: | - 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 - - run: - name: smoke test jupyterhub - command: | - docker run --rm -it jupyterhub/jupyterhub jupyterhub --help - - run: - name: verify static files - command: | - docker run --rm -it -v $PWD/dockerfiles:/io jupyterhub/jupyterhub python3 /io/test.py - -# Tell CircleCI to use this workflow when it builds the site -workflows: - version: 2 - default: - jobs: - - build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a172ee5c..bbee9967 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,3 +66,120 @@ jobs: run: | pip install twine twine upload --skip-existing dist/* + + publish-docker: + runs-on: ubuntu-20.04 + + services: + # So that we can test this in PRs/branches + local-registry: + image: registry:2 + ports: + - 5000:5000 + + steps: + - name: Should we push this image to a public registry? + run: | + if [ "${{ startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/master') }}" = "true" ]; then + # Empty => Docker Hub + echo "REGISTRY=" >> $GITHUB_ENV + else + echo "REGISTRY=localhost:5000/" >> $GITHUB_ENV + fi + + - uses: actions/checkout@v2 + + # Setup docker to build for multiple platforms, see: + # https://github.com/docker/build-push-action/tree/v2.4.0#usage + # https://github.com/docker/build-push-action/blob/v2.4.0/docs/advanced/multi-platform.md + + - name: Set up QEMU (for docker buildx) + uses: docker/setup-qemu-action@25f0500ff22e406f7191a2a8ba8cda16901ca018 # associated tag: v1.0.2 + + - name: Set up Docker Buildx (for multi-arch builds) + uses: docker/setup-buildx-action@2a4b53665e15ce7d7049afb11ff1f70ff1610609 # associated tag: v1.1.2 + with: + # Allows pushing to registry on localhost:5000 + driver-opts: network=host + + - name: Setup push rights to Docker Hub + # This was setup by... + # 1. Creating a Docker Hub service account "jupyterhubbot" + # 2. Creating a access token for the service account specific to this + # repository: https://hub.docker.com/settings/security + # 3. Making the account part of the "bots" team, and granting that team + # permissions to push to the relevant images: + # https://hub.docker.com/orgs/jupyterhub/teams/bots/permissions + # 4. Registering the username and token as a secret for this repo: + # https://github.com/jupyterhub/jupyterhub/settings/secrets/actions + if: env.REGISTRY != 'localhost:5000/' + run: | + docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKERHUB_TOKEN }}" + + # https://github.com/jupyterhub/action-major-minor-tag-calculator + # If this is a tagged build this will return additional parent tags. + # E.g. 1.2.3 is expanded to Docker tags + # [{prefix}:1.2.3, {prefix}:1.2, {prefix}:1, {prefix}:latest] unless + # this is a backported tag in which case the newer tags aren't updated. + # For branches this will return the branch name. + # If GITHUB_TOKEN isn't available (e.g. in PRs) returns no tags []. + - name: Get list of jupyterhub tags + id: jupyterhubtags + uses: jupyterhub/action-major-minor-tag-calculator@v1 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub:" + defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub:noref" + + - name: Build and push jupyterhub + uses: docker/build-push-action@e1b7f96249f2e4c8e4ac1519b9608c0d48944a1f # associated tag: v2.4.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + # tags parameter must be a string input so convert `gettags` JSON + # array into a comma separated list of tags + tags: ${{ join(fromJson(steps.jupyterhubtags.outputs.tags)) }} + + # jupyterhub-onbuild + + - name: Get list of jupyterhub-onbuild tags + id: onbuildtags + uses: jupyterhub/action-major-minor-tag-calculator@v1 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub-onbuild:" + defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub-onbuild:noref" + + - name: Build and push jupyterhub-onbuild + uses: docker/build-push-action@e1b7f96249f2e4c8e4ac1519b9608c0d48944a1f # associated tag: v2.4.0 + with: + build-args: | + BASE_IMAGE=${{ fromJson(steps.jupyterhubtags.outputs.tags)[0] }} + context: onbuild + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ join(fromJson(steps.onbuildtags.outputs.tags)) }} + + # jupyterhub-demo + + - name: Get list of jupyterhub-demo tags + id: demotags + uses: jupyterhub/action-major-minor-tag-calculator@v1 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + prefix: "${{ env.REGISTRY }}jupyterhub/jupyterhub-demo:" + defaultTag: "${{ env.REGISTRY }}jupyterhub/jupyterhub-demo:noref" + + - name: Build and push jupyterhub-demo + uses: docker/build-push-action@e1b7f96249f2e4c8e4ac1519b9608c0d48944a1f # associated tag: v2.4.0 + with: + build-args: | + BASE_IMAGE=${{ fromJson(steps.onbuildtags.outputs.tags)[0] }} + context: demo-image + # linux/arm64 currently fails: + # ERROR: Could not build wheels for argon2-cffi which use PEP 517 and cannot be installed directly + # ERROR: executor failed running [/bin/sh -c python3 -m pip install notebook]: exit code: 1 + platforms: linux/amd64 + push: true + tags: ${{ join(fromJson(steps.demotags.outputs.tags)) }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e83e381..a67c7bdf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -222,3 +222,25 @@ jobs: - name: Submit codecov report run: | codecov + + docker-build: + runs-on: ubuntu-20.04 + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v2 + + - name: build images + run: | + 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 + + - name: smoke test jupyterhub + run: | + docker run --rm -t jupyterhub/jupyterhub jupyterhub --help + + - name: verify static files + run: | + docker run --rm -t -v $PWD/dockerfiles:/io jupyterhub/jupyterhub python3 /io/test.py diff --git a/Dockerfile b/Dockerfile index ab7fca68..1290e05a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # your jupyterhub_config.py will be added automatically # from your docker directory. -ARG BASE_IMAGE=ubuntu:focal-20200729@sha256:6f2fb2f9fb5582f8b587837afd6ea8f37d8d1d9e41168c90f410a6ef15fa8ce5 +ARG BASE_IMAGE=ubuntu:focal-20200729 FROM $BASE_IMAGE AS builder USER root diff --git a/docs/rest-api.yml b/docs/rest-api.yml index 9f40f6da..49963d54 100644 --- a/docs/rest-api.yml +++ b/docs/rest-api.yml @@ -3,7 +3,7 @@ swagger: "2.0" info: title: JupyterHub description: The REST API for JupyterHub - version: 1.2.0dev + version: 1.4.0 license: name: BSD-3-Clause schemes: [http, https] diff --git a/docs/source/changelog.md b/docs/source/changelog.md index b12a018a..fd2be09a 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -13,6 +13,8 @@ and new configuration options. There are no database schema changes requiring migration from 1.3 to 1.4. +1.4 is also the first version to start publishing docker images for arm64. + In particular, OAuth tokens stored in user cookies, used for accessing single-user servers and hub-authenticated services, have changed their expiration from one hour to the expiry of the cookie @@ -22,13 +24,17 @@ This is now also configurable via `JupyterHub.oauth_token_expires_in`. The result is that it should be much less likely for auth tokens stored in cookies to expire during the lifetime of a server. -### 1.4.0 +### 1.4.0 2021-04-19 -([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.3.0...6121411aec529bac40f2535591ae36d1fc0e8df0)) +([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.3.0...1.4.0)) #### New features added +- Support Proxy.extra_routes [#3430](https://github.com/jupyterhub/jupyterhub/pull/3430) ([@yuvipanda](https://github.com/yuvipanda)) +- login-template: Add a "login_container" block inside the div-container. [#3422](https://github.com/jupyterhub/jupyterhub/pull/3422) ([@olifre](https://github.com/olifre)) +- Docker arm64 builds [#3421](https://github.com/jupyterhub/jupyterhub/pull/3421) ([@manics](https://github.com/manics)) - make oauth token expiry configurable [#3411](https://github.com/jupyterhub/jupyterhub/pull/3411) ([@minrk](https://github.com/minrk)) +- allow the hub to not be the default route [#3373](https://github.com/jupyterhub/jupyterhub/pull/3373) ([@minrk](https://github.com/minrk)) - Allow customization of service menu via templates [#3345](https://github.com/jupyterhub/jupyterhub/pull/3345) ([@stv0g](https://github.com/stv0g)) - Add Spawner.delete_forever [#3337](https://github.com/jupyterhub/jupyterhub/pull/3337) ([@nsshah1288](https://github.com/nsshah1288)) - Allow to set spawner-specific hub connect URL [#3326](https://github.com/jupyterhub/jupyterhub/pull/3326) ([@dtaniwaki](https://github.com/dtaniwaki)) @@ -36,6 +42,7 @@ to expire during the lifetime of a server. #### Enhancements made +- Log the exception raised in Spawner.post_stop_hook instead of raising it [#3418](https://github.com/jupyterhub/jupyterhub/pull/3418) ([@jiajunjie](https://github.com/jiajunjie)) - Don't delete all oauth clients on startup [#3407](https://github.com/jupyterhub/jupyterhub/pull/3407) ([@yuvipanda](https://github.com/yuvipanda)) - Use 'secrets' module to generate secrets [#3394](https://github.com/jupyterhub/jupyterhub/pull/3394) ([@yuvipanda](https://github.com/yuvipanda)) - Allow cookie_secret to be set to a hexadecimal string [#3343](https://github.com/jupyterhub/jupyterhub/pull/3343) ([@consideRatio](https://github.com/consideRatio)) @@ -51,6 +58,9 @@ to expire during the lifetime of a server. #### Maintenance and upkeep improvements +- typos in onbuild, demo images for push [#3429](https://github.com/jupyterhub/jupyterhub/pull/3429) ([@minrk](https://github.com/minrk)) +- Disable docker jupyterhub-demo arm64 build [#3425](https://github.com/jupyterhub/jupyterhub/pull/3425) ([@manics](https://github.com/manics)) +- Docker arm64 builds [#3421](https://github.com/jupyterhub/jupyterhub/pull/3421) ([@manics](https://github.com/manics)) - avoid deprecated engine.table_names [#3392](https://github.com/jupyterhub/jupyterhub/pull/3392) ([@minrk](https://github.com/minrk)) - alpine dockerfile: avoid compilation by getting some deps from apk [#3386](https://github.com/jupyterhub/jupyterhub/pull/3386) ([@minrk](https://github.com/minrk)) - Fix sqlachemy.interfaces.PoolListener deprecation for tests [#3383](https://github.com/jupyterhub/jupyterhub/pull/3383) ([@IvanaH8](https://github.com/IvanaH8)) @@ -65,6 +75,10 @@ to expire during the lifetime of a server. #### Documentation improvements +- DOC: Conform to numpydoc. [#3428](https://github.com/jupyterhub/jupyterhub/pull/3428) ([@Carreau](https://github.com/Carreau)) +- Fix link to jupyterhub/jupyterhub-the-hard-way [#3417](https://github.com/jupyterhub/jupyterhub/pull/3417) ([@manics](https://github.com/manics)) +- Changelog for 1.4 [#3415](https://github.com/jupyterhub/jupyterhub/pull/3415) ([@minrk](https://github.com/minrk)) +- Fastapi example [#3403](https://github.com/jupyterhub/jupyterhub/pull/3403) ([@kafonek](https://github.com/kafonek)) - Added Azure AD as a supported authenticator. [#3401](https://github.com/jupyterhub/jupyterhub/pull/3401) ([@maxshowarth](https://github.com/maxshowarth)) - Remove the hard way guide [#3375](https://github.com/jupyterhub/jupyterhub/pull/3375) ([@manics](https://github.com/manics)) - :memo: Fix telemetry section [#3333](https://github.com/jupyterhub/jupyterhub/pull/3333) ([@trallard](https://github.com/trallard)) @@ -75,9 +89,9 @@ to expire during the lifetime of a server. #### Contributors to this release -([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2020-12-11&to=2021-04-12&type=c)) +([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2020-12-11&to=2021-04-19&type=c)) -[@00Kai0](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A00Kai0+updated%3A2020-12-11..2021-04-12&type=Issues) | [@0mar](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A0mar+updated%3A2020-12-11..2021-04-12&type=Issues) | [@8rV1n](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A8rV1n+updated%3A2020-12-11..2021-04-12&type=Issues) | [@akhilputhiry](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aakhilputhiry+updated%3A2020-12-11..2021-04-12&type=Issues) | [@alexal](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalexal+updated%3A2020-12-11..2021-04-12&type=Issues) | [@analytically](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aanalytically+updated%3A2020-12-11..2021-04-12&type=Issues) | [@andreamazzoni](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aandreamazzoni+updated%3A2020-12-11..2021-04-12&type=Issues) | [@andrewisplinghoff](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aandrewisplinghoff+updated%3A2020-12-11..2021-04-12&type=Issues) | [@BertR](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ABertR+updated%3A2020-12-11..2021-04-12&type=Issues) | [@betatim](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2020-12-11..2021-04-12&type=Issues) | [@bitnik](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abitnik+updated%3A2020-12-11..2021-04-12&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abollwyvl+updated%3A2020-12-11..2021-04-12&type=Issues) | [@carluri](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acarluri+updated%3A2020-12-11..2021-04-12&type=Issues) | [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2020-12-11..2021-04-12&type=Issues) | [@davidedelvento](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adavidedelvento+updated%3A2020-12-11..2021-04-12&type=Issues) | [@dhirschfeld](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adhirschfeld+updated%3A2020-12-11..2021-04-12&type=Issues) | [@dmpe](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Admpe+updated%3A2020-12-11..2021-04-12&type=Issues) | [@dsblank](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adsblank+updated%3A2020-12-11..2021-04-12&type=Issues) | [@dtaniwaki](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adtaniwaki+updated%3A2020-12-11..2021-04-12&type=Issues) | [@elgalu](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aelgalu+updated%3A2020-12-11..2021-04-12&type=Issues) | [@eran-pinhas](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aeran-pinhas+updated%3A2020-12-11..2021-04-12&type=Issues) | [@gaebor](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agaebor+updated%3A2020-12-11..2021-04-12&type=Issues) | [@GeorgianaElena](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2020-12-11..2021-04-12&type=Issues) | [@gsemet](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agsemet+updated%3A2020-12-11..2021-04-12&type=Issues) | [@gweis](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agweis+updated%3A2020-12-11..2021-04-12&type=Issues) | [@hynek2001](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ahynek2001+updated%3A2020-12-11..2021-04-12&type=Issues) | [@ianabc](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aianabc+updated%3A2020-12-11..2021-04-12&type=Issues) | [@ibre5041](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aibre5041+updated%3A2020-12-11..2021-04-12&type=Issues) | [@IvanaH8](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AIvanaH8+updated%3A2020-12-11..2021-04-12&type=Issues) | [@jhegedus42](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajhegedus42+updated%3A2020-12-11..2021-04-12&type=Issues) | [@jhermann](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajhermann+updated%3A2020-12-11..2021-04-12&type=Issues) | [@jiajunjie](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajiajunjie+updated%3A2020-12-11..2021-04-12&type=Issues) | [@jtlz2](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajtlz2+updated%3A2020-12-11..2021-04-12&type=Issues) | [@katsar0v](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akatsar0v+updated%3A2020-12-11..2021-04-12&type=Issues) | [@kinow](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akinow+updated%3A2020-12-11..2021-04-12&type=Issues) | [@krinsman](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akrinsman+updated%3A2020-12-11..2021-04-12&type=Issues) | [@laurensdv](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Alaurensdv+updated%3A2020-12-11..2021-04-12&type=Issues) | [@lits789](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Alits789+updated%3A2020-12-11..2021-04-12&type=Issues) | [@m-alekseev](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Am-alekseev+updated%3A2020-12-11..2021-04-12&type=Issues) | [@mabbasi90](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amabbasi90+updated%3A2020-12-11..2021-04-12&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2020-12-11..2021-04-12&type=Issues) | [@manniche](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanniche+updated%3A2020-12-11..2021-04-12&type=Issues) | [@maxshowarth](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amaxshowarth+updated%3A2020-12-11..2021-04-12&type=Issues) | [@mdivk](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amdivk+updated%3A2020-12-11..2021-04-12&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2020-12-11..2021-04-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2020-12-11..2021-04-12&type=Issues) | [@mogthesprog](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amogthesprog+updated%3A2020-12-11..2021-04-12&type=Issues) | [@mriedem](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amriedem+updated%3A2020-12-11..2021-04-12&type=Issues) | [@nsshah1288](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ansshah1288+updated%3A2020-12-11..2021-04-12&type=Issues) | [@PandaWhoCodes](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3APandaWhoCodes+updated%3A2020-12-11..2021-04-12&type=Issues) | [@pawsaw](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apawsaw+updated%3A2020-12-11..2021-04-12&type=Issues) | [@phozzy](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aphozzy+updated%3A2020-12-11..2021-04-12&type=Issues) | [@playermanny2](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aplayermanny2+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rabsr](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arabsr+updated%3A2020-12-11..2021-04-12&type=Issues) | [@randy3k](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arandy3k+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rawrgulmuffins](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arawrgulmuffins+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rcthomas](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arcthomas+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rebeca-maia](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arebeca-maia+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rebenkoy](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arebenkoy+updated%3A2020-12-11..2021-04-12&type=Issues) | [@rkdarst](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arkdarst+updated%3A2020-12-11..2021-04-12&type=Issues) | [@robnagler](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arobnagler+updated%3A2020-12-11..2021-04-12&type=Issues) | [@ronaldpetty](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aronaldpetty+updated%3A2020-12-11..2021-04-12&type=Issues) | [@ryanlovett](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2020-12-11..2021-04-12&type=Issues) | [@ryogesh](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryogesh+updated%3A2020-12-11..2021-04-12&type=Issues) | [@sbailey-auro](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asbailey-auro+updated%3A2020-12-11..2021-04-12&type=Issues) | [@sigurdurb](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asigurdurb+updated%3A2020-12-11..2021-04-12&type=Issues) | [@SivaAccionLabs](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASivaAccionLabs+updated%3A2020-12-11..2021-04-12&type=Issues) | [@sougou](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asougou+updated%3A2020-12-11..2021-04-12&type=Issues) | [@stv0g](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astv0g+updated%3A2020-12-11..2021-04-12&type=Issues) | [@sudi007](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asudi007+updated%3A2020-12-11..2021-04-12&type=Issues) | [@support](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asupport+updated%3A2020-12-11..2021-04-12&type=Issues) | [@tathagata](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atathagata+updated%3A2020-12-11..2021-04-12&type=Issues) | [@timgates42](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atimgates42+updated%3A2020-12-11..2021-04-12&type=Issues) | [@trallard](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atrallard+updated%3A2020-12-11..2021-04-12&type=Issues) | [@vlizanae](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avlizanae+updated%3A2020-12-11..2021-04-12&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awelcome+updated%3A2020-12-11..2021-04-12&type=Issues) | [@whitespaceninja](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awhitespaceninja+updated%3A2020-12-11..2021-04-12&type=Issues) | [@whlteXbread](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AwhlteXbread+updated%3A2020-12-11..2021-04-12&type=Issues) | [@willingc](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awillingc+updated%3A2020-12-11..2021-04-12&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2020-12-11..2021-04-12&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AZsailer+updated%3A2020-12-11..2021-04-12&type=Issues) +[@00Kai0](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A00Kai0+updated%3A2020-12-11..2021-04-19&type=Issues) | [@8rV1n](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A8rV1n+updated%3A2020-12-11..2021-04-19&type=Issues) | [@akhilputhiry](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aakhilputhiry+updated%3A2020-12-11..2021-04-19&type=Issues) | [@alexal](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalexal+updated%3A2020-12-11..2021-04-19&type=Issues) | [@analytically](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aanalytically+updated%3A2020-12-11..2021-04-19&type=Issues) | [@andreamazzoni](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aandreamazzoni+updated%3A2020-12-11..2021-04-19&type=Issues) | [@andrewisplinghoff](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aandrewisplinghoff+updated%3A2020-12-11..2021-04-19&type=Issues) | [@BertR](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ABertR+updated%3A2020-12-11..2021-04-19&type=Issues) | [@betatim](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2020-12-11..2021-04-19&type=Issues) | [@bitnik](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abitnik+updated%3A2020-12-11..2021-04-19&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abollwyvl+updated%3A2020-12-11..2021-04-19&type=Issues) | [@carluri](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acarluri+updated%3A2020-12-11..2021-04-19&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ACarreau+updated%3A2020-12-11..2021-04-19&type=Issues) | [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2020-12-11..2021-04-19&type=Issues) | [@davidedelvento](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adavidedelvento+updated%3A2020-12-11..2021-04-19&type=Issues) | [@dhirschfeld](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adhirschfeld+updated%3A2020-12-11..2021-04-19&type=Issues) | [@dmpe](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Admpe+updated%3A2020-12-11..2021-04-19&type=Issues) | [@dsblank](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adsblank+updated%3A2020-12-11..2021-04-19&type=Issues) | [@dtaniwaki](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adtaniwaki+updated%3A2020-12-11..2021-04-19&type=Issues) | [@echarles](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aecharles+updated%3A2020-12-11..2021-04-19&type=Issues) | [@elgalu](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aelgalu+updated%3A2020-12-11..2021-04-19&type=Issues) | [@eran-pinhas](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aeran-pinhas+updated%3A2020-12-11..2021-04-19&type=Issues) | [@gaebor](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agaebor+updated%3A2020-12-11..2021-04-19&type=Issues) | [@GeorgianaElena](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2020-12-11..2021-04-19&type=Issues) | [@gsemet](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agsemet+updated%3A2020-12-11..2021-04-19&type=Issues) | [@gweis](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Agweis+updated%3A2020-12-11..2021-04-19&type=Issues) | [@hynek2001](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ahynek2001+updated%3A2020-12-11..2021-04-19&type=Issues) | [@ianabc](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aianabc+updated%3A2020-12-11..2021-04-19&type=Issues) | [@ibre5041](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aibre5041+updated%3A2020-12-11..2021-04-19&type=Issues) | [@IvanaH8](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AIvanaH8+updated%3A2020-12-11..2021-04-19&type=Issues) | [@jhegedus42](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajhegedus42+updated%3A2020-12-11..2021-04-19&type=Issues) | [@jhermann](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajhermann+updated%3A2020-12-11..2021-04-19&type=Issues) | [@jiajunjie](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajiajunjie+updated%3A2020-12-11..2021-04-19&type=Issues) | [@jtlz2](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ajtlz2+updated%3A2020-12-11..2021-04-19&type=Issues) | [@kafonek](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akafonek+updated%3A2020-12-11..2021-04-19&type=Issues) | [@katsar0v](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akatsar0v+updated%3A2020-12-11..2021-04-19&type=Issues) | [@kinow](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akinow+updated%3A2020-12-11..2021-04-19&type=Issues) | [@krinsman](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Akrinsman+updated%3A2020-12-11..2021-04-19&type=Issues) | [@laurensdv](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Alaurensdv+updated%3A2020-12-11..2021-04-19&type=Issues) | [@lits789](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Alits789+updated%3A2020-12-11..2021-04-19&type=Issues) | [@m-alekseev](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Am-alekseev+updated%3A2020-12-11..2021-04-19&type=Issues) | [@mabbasi90](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amabbasi90+updated%3A2020-12-11..2021-04-19&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2020-12-11..2021-04-19&type=Issues) | [@manniche](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanniche+updated%3A2020-12-11..2021-04-19&type=Issues) | [@maxshowarth](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amaxshowarth+updated%3A2020-12-11..2021-04-19&type=Issues) | [@mdivk](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amdivk+updated%3A2020-12-11..2021-04-19&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2020-12-11..2021-04-19&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2020-12-11..2021-04-19&type=Issues) | [@mogthesprog](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amogthesprog+updated%3A2020-12-11..2021-04-19&type=Issues) | [@mriedem](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amriedem+updated%3A2020-12-11..2021-04-19&type=Issues) | [@nsshah1288](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ansshah1288+updated%3A2020-12-11..2021-04-19&type=Issues) | [@olifre](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aolifre+updated%3A2020-12-11..2021-04-19&type=Issues) | [@PandaWhoCodes](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3APandaWhoCodes+updated%3A2020-12-11..2021-04-19&type=Issues) | [@pawsaw](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apawsaw+updated%3A2020-12-11..2021-04-19&type=Issues) | [@phozzy](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aphozzy+updated%3A2020-12-11..2021-04-19&type=Issues) | [@playermanny2](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aplayermanny2+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rabsr](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arabsr+updated%3A2020-12-11..2021-04-19&type=Issues) | [@randy3k](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arandy3k+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rawrgulmuffins](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arawrgulmuffins+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rcthomas](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arcthomas+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rebeca-maia](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arebeca-maia+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rebenkoy](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arebenkoy+updated%3A2020-12-11..2021-04-19&type=Issues) | [@rkdarst](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arkdarst+updated%3A2020-12-11..2021-04-19&type=Issues) | [@robnagler](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Arobnagler+updated%3A2020-12-11..2021-04-19&type=Issues) | [@ronaldpetty](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aronaldpetty+updated%3A2020-12-11..2021-04-19&type=Issues) | [@ryanlovett](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2020-12-11..2021-04-19&type=Issues) | [@ryogesh](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryogesh+updated%3A2020-12-11..2021-04-19&type=Issues) | [@sbailey-auro](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asbailey-auro+updated%3A2020-12-11..2021-04-19&type=Issues) | [@sigurdurb](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asigurdurb+updated%3A2020-12-11..2021-04-19&type=Issues) | [@SivaAccionLabs](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASivaAccionLabs+updated%3A2020-12-11..2021-04-19&type=Issues) | [@sougou](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asougou+updated%3A2020-12-11..2021-04-19&type=Issues) | [@stv0g](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astv0g+updated%3A2020-12-11..2021-04-19&type=Issues) | [@sudi007](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asudi007+updated%3A2020-12-11..2021-04-19&type=Issues) | [@support](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asupport+updated%3A2020-12-11..2021-04-19&type=Issues) | [@tathagata](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atathagata+updated%3A2020-12-11..2021-04-19&type=Issues) | [@timgates42](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atimgates42+updated%3A2020-12-11..2021-04-19&type=Issues) | [@trallard](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Atrallard+updated%3A2020-12-11..2021-04-19&type=Issues) | [@vlizanae](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avlizanae+updated%3A2020-12-11..2021-04-19&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awelcome+updated%3A2020-12-11..2021-04-19&type=Issues) | [@whitespaceninja](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awhitespaceninja+updated%3A2020-12-11..2021-04-19&type=Issues) | [@whlteXbread](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AwhlteXbread+updated%3A2020-12-11..2021-04-19&type=Issues) | [@willingc](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awillingc+updated%3A2020-12-11..2021-04-19&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2020-12-11..2021-04-19&type=Issues) | [@Zsailer](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AZsailer+updated%3A2020-12-11..2021-04-19&type=Issues) ### 1.3 @@ -1027,7 +1041,8 @@ Fix removal of `/login` page in 0.4.0, breaking some OAuth providers. First preview release -[unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.3.0...HEAD +[unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.4.0...HEAD +[1.4.0]: https://github.com/jupyterhub/jupyterhub/compare/1.3.0...1.4.0 [1.3.0]: https://github.com/jupyterhub/jupyterhub/compare/1.2.1...1.3.0 [1.2.2]: https://github.com/jupyterhub/jupyterhub/compare/1.2.1...1.2.2 [1.2.1]: https://github.com/jupyterhub/jupyterhub/compare/1.2.0...1.2.1 diff --git a/hooks/README.md b/hooks/README.md deleted file mode 100644 index 6cdbadad..00000000 --- a/hooks/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Docker Cloud build hooks - -These are the hooks diff --git a/hooks/post_build b/hooks/post_build deleted file mode 100755 index 70d712e2..00000000 --- a/hooks/post_build +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -exuo pipefail - -# build jupyterhub-onbuild image -docker build --build-arg BASE_IMAGE=$DOCKER_REPO:$DOCKER_TAG -t ${DOCKER_REPO}-onbuild:$DOCKER_TAG onbuild -# build jupyterhub-demo image -docker build --build-arg BASE_IMAGE=${DOCKER_REPO}-onbuild:$DOCKER_TAG -t ${DOCKER_REPO}-demo:$DOCKER_TAG demo-image diff --git a/hooks/post_push b/hooks/post_push deleted file mode 100755 index 4db84638..00000000 --- a/hooks/post_push +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -set -exuo pipefail - -export ONBUILD=${DOCKER_REPO}-onbuild -export DEMO=${DOCKER_REPO}-demo -export REPOS="${DOCKER_REPO} ${ONBUILD} ${DEMO}" -# push ONBUILD image -docker push $ONBUILD:$DOCKER_TAG -docker push $DEMO:$DOCKER_TAG - -function get_hub_version() { - rm -f hub_version - docker run --rm -v $PWD:/version -u $(id -u) -i $DOCKER_REPO:$DOCKER_TAG sh -c 'jupyterhub --version > /version/hub_version' - hub_xyz=$(cat hub_version) - split=( ${hub_xyz//./ } ) - hub_xy="${split[0]}.${split[1]}" - # add .dev on hub_xy so it's 1.0.dev - if [[ ! -z "${split[3]:-}" ]]; then - hub_xy="${hub_xy}.${split[3]}" - latest=0 - else - latest=1 - fi -} - -get_hub_version - -for repo in ${REPOS}; do - # when building master, push 0.9.0.dev as well - docker tag $repo:$DOCKER_TAG $repo:$hub_xyz - docker push $repo:$hub_xyz - - # when building 0.9.x, push 0.9 as well - docker tag $repo:$DOCKER_TAG $repo:$hub_xy - docker push $repo:$hub_xy - - # if building a stable release, tag latest as well - if [[ "$latest" == "1" ]]; then - docker tag $repo:$DOCKER_TAG $repo:latest - docker push $repo:latest - fi -done diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 76dd0df3..86d904ef 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -885,6 +885,66 @@ class JupyterHub(Application): def _hub_prefix_default(self): return url_path_join(self.base_url, '/hub/') + hub_routespec = Unicode( + "/", + help=""" + The routing prefix for the Hub itself. + + Override to send only a subset of traffic to the Hub. + Default is to use the Hub as the default route for all requests. + + This is necessary for normal jupyterhub operation, + as the Hub must receive requests for e.g. `/user/:name` + when the user's server is not running. + + However, some deployments using only the JupyterHub API + may want to handle these events themselves, + in which case they can register their own default target with the proxy + and set e.g. `hub_routespec = /hub/` to serve only the hub's own pages, or even `/hub/api/` for api-only operation. + + Note: hub_routespec must include the base_url, if any. + + .. versionadded:: 1.4 + """, + ).tag(config=True) + + @default("hub_routespec") + def _default_hub_routespec(self): + # Default routespec for the Hub is the *app* base url + # not the hub URL, so the Hub receives requests for non-running servers + # use `/` with host-based routing so the Hub + # gets requests for all hosts + if self.subdomain_host: + routespec = '/' + else: + routespec = self.base_url + return routespec + + @validate("hub_routespec") + def _validate_hub_routespec(self, proposal): + """ensure leading/trailing / on custom routespec prefix + + - trailing '/' always required + - leading '/' required unless using subdomains + """ + routespec = proposal.value + if not routespec.endswith("/"): + routespec = routespec + "/" + if not self.subdomain_host and not routespec.startswith("/"): + routespec = "/" + routespec + return routespec + + @observe("hub_routespec") + def _hub_routespec_changed(self, change): + if change.new == change.old: + return + routespec = change.new + if routespec not in {'/', self.base_url}: + self.log.warning( + f"Using custom route for Hub: {routespec}." + " Requests for not-running servers may not be handled." + ) + @observe('base_url') def _update_hub_prefix(self, change): """add base URL to hub prefix""" @@ -1718,6 +1778,7 @@ class JupyterHub(Application): """Load the Hub URL config""" hub_args = dict( base_url=self.hub_prefix, + routespec=self.hub_routespec, public_host=self.subdomain_host, certfile=self.internal_ssl_cert, keyfile=self.internal_ssl_key, @@ -1733,17 +1794,15 @@ class JupyterHub(Application): hub_args['ip'] = self.hub_ip hub_args['port'] = self.hub_port - # routespec for the Hub is the *app* base url - # not the hub URL, so it receives requests for non-running servers - # use `/` with host-based routing so the Hub - # gets requests for all hosts - host = '' - if self.subdomain_host: - routespec = '/' - else: - routespec = self.base_url + self.hub = Hub(**hub_args) - self.hub = Hub(routespec=routespec, **hub_args) + if not self.subdomain_host: + api_prefix = url_path_join(self.hub.base_url, "api/") + if not api_prefix.startswith(self.hub.routespec): + self.log.warning( + f"Hub API prefix {api_prefix} not on prefix {self.hub.routespec}. " + "The Hub may not receive any API requests from outside." + ) if self.hub_connect_ip: self.hub.connect_ip = self.hub_connect_ip diff --git a/jupyterhub/dbutil.py b/jupyterhub/dbutil.py index e3fc7850..fd67e941 100644 --- a/jupyterhub/dbutil.py +++ b/jupyterhub/dbutil.py @@ -26,10 +26,9 @@ def write_alembic_ini(alembic_ini='alembic.ini', db_url='sqlite:///jupyterhub.sq Parameters ---------- - - alembic_ini: str + alembic_ini : str path to the alembic.ini file that should be written. - db_url: str + db_url : str The SQLAlchemy database url, e.g. `sqlite:///jupyterhub.sqlite`. """ with open(ALEMBIC_INI_TEMPLATE_PATH) as f: @@ -58,13 +57,11 @@ def _temp_alembic_ini(db_url): Parameters ---------- - - db_url: str + db_url : str The SQLAlchemy database url, e.g. `sqlite:///jupyterhub.sqlite`. Returns ------- - alembic_ini: str The path to the temporary alembic.ini that we have created. This file will be cleaned up on exit from the context manager. diff --git a/jupyterhub/handlers/metrics.py b/jupyterhub/handlers/metrics.py index 0f63d9c3..d2f0b03b 100644 --- a/jupyterhub/handlers/metrics.py +++ b/jupyterhub/handlers/metrics.py @@ -1,3 +1,4 @@ +"""Handlers for serving prometheus metrics""" from prometheus_client import CONTENT_TYPE_LATEST from prometheus_client import generate_latest from prometheus_client import REGISTRY @@ -17,4 +18,7 @@ class MetricsHandler(BaseHandler): self.write(generate_latest(REGISTRY)) -default_handlers = [(r'/metrics$', MetricsHandler)] +default_handlers = [ + (r'/metrics$', MetricsHandler), + (r'/api/metrics$', MetricsHandler), +] diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index 356aae71..3d2b03f6 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -676,4 +676,5 @@ default_handlers = [ (r'/token', TokenPageHandler), (r'/error/(\d+)', ProxyErrorHandler), (r'/health$', HealthCheckHandler), + (r'/api/health$', HealthCheckHandler), ] diff --git a/jupyterhub/proxy.py b/jupyterhub/proxy.py index cf707d6f..82587191 100644 --- a/jupyterhub/proxy.py +++ b/jupyterhub/proxy.py @@ -32,6 +32,7 @@ from tornado.ioloop import PeriodicCallback from traitlets import Any from traitlets import Bool from traitlets import default +from traitlets import Dict from traitlets import Instance from traitlets import Integer from traitlets import observe @@ -112,6 +113,26 @@ class Proxy(LoggingConfigurable): """, ) + extra_routes = Dict( + {}, + config=True, + help=""" + Additional routes to be maintained in the proxy. + + A dictionary with a route specification as key, and + a URL as target. The hub will ensure this route is present + in the proxy. + + If the hub is running in host based mode (with + JupyterHub.subdomain_host set), the routespec *must* + have a domain component (example.com/my-url/). If the + hub is not running in host based mode, the routespec + *must not* have a domain component (/my-url/). + + Helpful when the hub is running in API-only mode. + """, + ) + def start(self): """Start the proxy. @@ -330,7 +351,7 @@ class Proxy(LoggingConfigurable): route = routes[self.app.hub.routespec] if route['target'] != hub.host: self.log.warning( - "Updating default route %s → %s", route['target'], hub.host + "Updating Hub route %s → %s", route['target'], hub.host ) futures.append(self.add_hub_route(hub)) @@ -384,6 +405,11 @@ class Proxy(LoggingConfigurable): ) futures.append(self.add_service(service)) + # Add extra routes we've been configured for + for routespec, url in self.extra_routes.items(): + good_routes.add(routespec) + futures.append(self.add_route(routespec, url, {'extra': True})) + # Now delete the routes that shouldn't be there for routespec in routes: if routespec not in good_routes: @@ -396,7 +422,7 @@ class Proxy(LoggingConfigurable): def add_hub_route(self, hub): """Add the default route for the Hub""" - self.log.info("Adding default route for Hub: %s => %s", hub.routespec, hub.host) + self.log.info("Adding route for Hub: %s => %s", hub.routespec, hub.host) return self.add_route(hub.routespec, self.hub.host, {'hub': True}) async def restore_routes(self): diff --git a/jupyterhub/services/auth.py b/jupyterhub/services/auth.py index 7b7320cb..bcc8a57b 100644 --- a/jupyterhub/services/auth.py +++ b/jupyterhub/services/auth.py @@ -668,12 +668,15 @@ class HubOAuth(HubAuth): Parameters ---------- - handler (RequestHandler): A tornado RequestHandler - next_url (str): The page to redirect to on successful login + handler : RequestHandler + A tornado RequestHandler + next_url : str + The page to redirect to on successful login Returns ------- - state (str): The OAuth state that has been stored in the cookie (url safe, base64-encoded) + state : str + The OAuth state that has been stored in the cookie (url safe, base64-encoded) """ extra_state = {} if handler.get_cookie(self.state_cookie_name): @@ -710,7 +713,8 @@ class HubOAuth(HubAuth): Parameters ---------- - next_url (str): The URL of the page to redirect to on successful login. + next_url : str + The URL of the page to redirect to on successful login. Returns ------- diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index dc335d47..ead537d6 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -729,7 +729,7 @@ class Spawner(LoggingConfigurable): Returns ------- state: dict - a JSONable dict of state + a JSONable dict of state """ state = {} return state diff --git a/jupyterhub/tests/test_app.py b/jupyterhub/tests/test_app.py index 06c18ba7..47f54165 100644 --- a/jupyterhub/tests/test_app.py +++ b/jupyterhub/tests/test_app.py @@ -1,5 +1,6 @@ """Test the JupyterHub entry point""" import binascii +import logging import os import re import sys @@ -329,3 +330,41 @@ def test_url_config(hub_config, expected): # validate additional properties for key, value in expected.items(): assert getattr(app, key) == value + + +@pytest.mark.parametrize( + "base_url, hub_routespec, expected_routespec, should_warn, bad_prefix", + [ + (None, None, "/", False, False), + ("/", "/", "/", False, False), + ("/base", "/base", "/base/", False, False), + ("/", "/hub", "/hub/", True, False), + (None, "hub/api", "/hub/api/", True, False), + ("/base", "/hub/", "/hub/", True, True), + (None, "/hub/api/health", "/hub/api/health/", True, True), + ], +) +def test_hub_routespec( + base_url, hub_routespec, expected_routespec, should_warn, bad_prefix, caplog +): + cfg = Config() + if base_url: + cfg.JupyterHub.base_url = base_url + if hub_routespec: + cfg.JupyterHub.hub_routespec = hub_routespec + with caplog.at_level(logging.WARNING): + app = JupyterHub(config=cfg, log=logging.getLogger()) + app.init_hub() + hub = app.hub + assert hub.routespec == expected_routespec + + if should_warn: + assert "custom route for Hub" in caplog.text + assert hub_routespec in caplog.text + else: + assert "custom route for Hub" not in caplog.text + + if bad_prefix: + assert "may not receive" in caplog.text + else: + assert "may not receive" not in caplog.text diff --git a/jupyterhub/tests/test_proxy.py b/jupyterhub/tests/test_proxy.py index 60f2d0e0..34af5c1d 100644 --- a/jupyterhub/tests/test_proxy.py +++ b/jupyterhub/tests/test_proxy.py @@ -195,6 +195,25 @@ async def test_check_routes(app, username, disable_check_routes): assert before == after +async def test_extra_routes(app): + proxy = app.proxy + # When using host_routing, it's up to the admin to + # provide routespecs that have a domain in them. + # We don't explicitly validate that here. + if app.subdomain_host: + route_spec = 'example.com/test-extra-routes/' + else: + route_spec = '/test-extra-routes/' + target = 'http://localhost:9999/test' + proxy.extra_routes = {route_spec: target} + + await proxy.check_routes(app.users, app._service_map) + + routes = await app.proxy.get_all_routes() + assert route_spec in routes + assert routes[route_spec]['target'] == target + + @pytest.mark.parametrize( "routespec", [ diff --git a/jupyterhub/tests/utils.py b/jupyterhub/tests/utils.py index dd47f93f..73ddcf51 100644 --- a/jupyterhub/tests/utils.py +++ b/jupyterhub/tests/utils.py @@ -72,9 +72,8 @@ def check_db_locks(func): The decorator relies on an instance of JupyterHubApp being the first argument to the decorated function. - Example - ------- - + Examples + -------- @check_db_locks def api_request(app, *api_path, **kwargs): diff --git a/onbuild/README.md b/onbuild/README.md index fae5e4fb..e1d6696e 100644 --- a/onbuild/README.md +++ b/onbuild/README.md @@ -2,7 +2,7 @@ If you base a Dockerfile on this image: - FROM jupyterhub/jupyterhub-onbuild:0.6 + FROM jupyterhub/jupyterhub-onbuild:1.4.0 ... then your `jupyterhub_config.py` adjacent to your Dockerfile will be loaded into the image and used by JupyterHub. diff --git a/share/jupyterhub/templates/login.html b/share/jupyterhub/templates/login.html index 98d43dc8..153a6c27 100644 --- a/share/jupyterhub/templates/login.html +++ b/share/jupyterhub/templates/login.html @@ -10,6 +10,7 @@ {% block login %}