diff --git a/.github/workflows/test-docker-build.yaml b/.github/workflows/test-docker-build.yaml deleted file mode 100644 index 86af0762a..000000000 --- a/.github/workflows/test-docker-build.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# This is a GitHub workflow defining a set of jobs with a set of steps. -# ref: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions -# -name: Test docker multiarch build - -# Trigger the workflow's on all PRs and pushes so that other contributors can -# run tests in their own forks. Avoid triggering this tests on changes not -# influencing the image notably. -on: - pull_request: - paths: - - "helm-chart/images/**" - - "helm-chart/chartpress.yaml" - - ".github/workflows/test-docker-build.yaml" - push: - paths: - - "helm-chart/images/**" - - "helm-chart/chartpress.yaml" - - ".github/workflows/test-docker-build.yaml" - branches-ignore: - - "dependabot/**" - - "pre-commit-ci-update-config" - - "update-*" - workflow_dispatch: - -jobs: - # This is a quick test to check the arm64 docker images based on: - # - https://github.com/docker/build-push-action/blob/v2.3.0/docs/advanced/local-registry.md - # - https://github.com/docker/build-push-action/blob/v2.3.0/docs/advanced/multi-platform.md - build_images: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - with: - # chartpress requires git history to set chart version and image tags - # correctly - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - uses: actions/setup-node@v4 - # node required to build wheel - with: - node-version: "22" - - - name: Install chartpress - run: pip install chartpress build - - - name: Build binderhub wheel - run: python3 -m build --wheel . - - - name: Set up QEMU (for docker buildx) - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx (for chartpress multi-arch builds) - uses: docker/setup-buildx-action@v3 - - - name: Build a multiple architecture Docker image - run: | - cd helm-chart - chartpress \ - --builder docker-buildx \ - --platform linux/amd64 --platform linux/arm64 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4ea4c5a3..e031181c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,3 @@ -# This is a GitHub workflow defining a set of jobs with a set of steps. -# ref: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions -# name: Tests on: @@ -27,17 +24,6 @@ on: workflow_dispatch: jobs: - lint: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - - name: install requirements - run: pip install ruamel.yaml - - - name: check embedded chart code - run: ./ci/check_embedded_chart_code.py - # Most of the "main", "auth" and "helm" jobs are the same and only differ # in small things. Unfortunately there is no easy way to share steps between # jobs or have "template" jobs, so we use `if` conditions on steps @@ -170,7 +156,7 @@ jobs: - name: Setup Python package dependencies run: | - pip install --no-binary pycurl -r dev-requirements.txt -r helm-chart/images/binderhub/requirements.txt + pip install --no-binary pycurl -r dev-requirements.txt pip install -e . - name: Install Playwright browser diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d3359231..debf01855 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,22 +31,6 @@ repos: - id: isort # args are not passed, but see the config in pyproject.toml - # Generated code: - # An entry in helm-chart/binderhub/values.yaml should be generated based on - # binderhub/binderspawner_mixin.py. See ci/check_embedded_chart_code.py for - # more details. - - repo: local - hooks: - - id: update-values-based-on-binderspawner-mixin - name: Update helm-chart/binderhub/values.yaml based on binderhub/binderspawner_mixin.py - language: python - additional_dependencies: ["ruamel.yaml"] - entry: python ci/check_embedded_chart_code.py - args: - - --update - files: binderhub/binderspawner_mixin.py|helm-chart/binderhub/values.yaml - pass_filenames: false - # Autoformat: js, html, markdown, yaml, json - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 diff --git a/binderhub/binderspawner_mixin.py b/binderhub/binderspawner_mixin.py index b0af376d4..b17fb2e1b 100644 --- a/binderhub/binderspawner_mixin.py +++ b/binderhub/binderspawner_mixin.py @@ -1,11 +1,6 @@ """ Helpers for creating BinderSpawners -FIXME: -This file is defined in binderhub/binderspawner_mixin.py -and is copied to helm-chart/binderhub/values.yaml -by ci/check_embedded_chart_code.py - The BinderHub repo is just used as the distribution mechanism for this spawner, BinderHub itself doesn't require this code. diff --git a/ci/check_embedded_chart_code.py b/ci/check_embedded_chart_code.py deleted file mode 100755 index 86b7a9374..000000000 --- a/ci/check_embedded_chart_code.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python - -# FIXME: We currently have some code duplicated in -# binderhub/binderspawner_mixin.py and helm-chart/binderhub/values.yaml -# and we use a pre-commit hook to automatically update the values in -# values.yaml. -# -# We should remove the embedded code from values.yaml and install the required -# BinderSpawner code in the JupyterHub container. -# - -# For now just check that the two are in sync -import argparse -import difflib -import os -import sys - -from ruamel.yaml import YAML - -yaml = YAML() -yaml.preserve_quotes = True -yaml.indent(mapping=2, sequence=4, offset=2) - -parser = argparse.ArgumentParser(description="Check embedded chart code") -parser.add_argument( - "--update", action="store_true", help="Update binderhub code from values.yaml" -) -args = parser.parse_args() - -root = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.pardir) -binderspawner_mixin_py = os.path.join(root, "binderhub", "binderspawner_mixin.py") -values_yaml = os.path.join(root, "helm-chart", "binderhub", "values.yaml") - -with open(binderspawner_mixin_py) as f: - py_code = f.read() - - -if args.update: - with open(values_yaml) as f: - values = yaml.load(f) - values_code = values["jupyterhub"]["hub"]["extraConfig"]["0-binderspawnermixin"] - if values_code != py_code: - print(f"Generating {values_yaml} from {binderspawner_mixin_py}") - values["jupyterhub"]["hub"]["extraConfig"]["0-binderspawnermixin"] = py_code - with open(values_yaml, "w") as f: - yaml.dump(values, f) -else: - with open(values_yaml) as f: - values = yaml.load(f) - values_code = values["jupyterhub"]["hub"]["extraConfig"]["0-binderspawnermixin"] - - difflines = list( - difflib.context_diff(values_code.splitlines(), py_code.splitlines()) - ) - if difflines: - print("\n".join(difflines)) - print("\n") - print("Values code is not in sync with binderhub/binderspawner_mixin.py") - print( - f"Run `python {sys.argv[0]} --update` to update values.yaml from binderhub/binderspawner_mixin.py" - ) - sys.exit(1) diff --git a/ci/refreeze b/ci/refreeze index 80a140f00..5c45b4043 100755 --- a/ci/refreeze +++ b/ci/refreeze @@ -7,8 +7,8 @@ set -xeuo pipefail docker run --rm \ --env=CUSTOM_COMPILE_COMMAND='Use the "Run workflow" button at https://github.com/jupyterhub/binderhub/actions/workflows/watch-dependencies.yaml' \ - --volume="$PWD:/io" \ - --workdir=/io \ + --volume="$PWD:/src" \ + --workdir=/src \ --user=root \ python:3.13-bookworm \ sh -c 'pip install pip-tools==7.* && pip-compile --allow-unsafe --strip-extras --upgrade helm-chart/images/binderhub/requirements.in' diff --git a/dev-config.yaml b/dev-config.yaml new file mode 100644 index 000000000..7d03d2086 --- /dev/null +++ b/dev-config.yaml @@ -0,0 +1,45 @@ +# NOTE: localhost:5000 will be replaced from the test job! +# +# When running tests, we use a local container registry to test pushing +# images to that can be reached from the build pods. This local registry +# is run outside of k8s, so use of localhost won't work well. Due to that, +# all values with localhost:5000 below are set explicitly from the test +# job to an IP that is reachable instead. +# +config: + # DockerRegistry is a jupyterhub/binderhub class that is used to call + # get_image_manifest, which determines if an image needs to be built or is + # already available in a registry. It will not need credentials to the + # registry. + # + DockerRegistry: + url: http://localhost:5000 + BinderHub: + # log_level isn't enabled to avoid overwhelming the reader with info + # log_level: DEBUG + use_registry: true + image_prefix: localhost:5000/binderhub/ +buildPodsRegistryCredentials: + server: http://localhost:5000 + username: "" + password: "" + +service: + type: NodePort + nodePort: 30080 + +dockerApi: + # The docker-api daemonset running the docker daemon needs to be configured in + # a way allowing it to work against insecure HTTP (not HTTPS) registries. + # + # ref: https://docs.docker.com/registry/insecure/ + # + extraArgs: + - --config-file=/etc/docker-api/daemon.json + extraFiles: + daemon-json: + mountPath: /etc/docker-api/daemon.json + data: + # debug isn't enabled to avoid overwhelming the reader with info + # debug: true + insecure-registries: [localhost:5000] diff --git a/helm-chart/.gitignore b/helm-chart/.gitignore deleted file mode 100644 index a25dc44b5..000000000 --- a/helm-chart/.gitignore +++ /dev/null @@ -1,134 +0,0 @@ -binderhub/values.schema.json - -### macOS ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon -# Thumbnails -._* -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Vim ### -# swap -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -# session -Session.vim -# temporary -.netrwhist -*~ -# auto-generated tag files -tags - -# GCloud Credentials -data8-travis-creds.json - -#### Python .gitignore from https://github.com/github/gitignore/blob/master/Python.gitignore -#### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject diff --git a/helm-chart/LICENSE b/helm-chart/LICENSE deleted file mode 100644 index aa6780e7c..000000000 --- a/helm-chart/LICENSE +++ /dev/null @@ -1,208 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 Allan Wu - Copyright 2016 Derrick Mar - Copyright 2016 Jeff Gong - Copyright 2016 Peter Veerman - Copyright 2016 Ryan Lovett - Copyright 2016 Sam Lau - Copyright 2016 Yuvi Panda - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/helm-chart/README.md b/helm-chart/README.md deleted file mode 100644 index 46b3f3f4f..000000000 --- a/helm-chart/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# BinderHub Helm Chart - -A [helm][] [chart][] for deploying [BinderHub] instances on [Kubernetes]. - -**[Zero to JupyterHub with Kubernetes]** provides detailed instructions for using this project within a JupyerHub deployment. - -## Overview of [Kubernetes] terminology - -### What is [helm]? - -[helm] is the Kubernetes package manager. [Helm] streamlines installing and managing Kubernetes applications. _Reference: [helm repo]_ - -### What is a [chart]? - -Charts are Helm packages that contain at least two things: - -- A description of the package (`Chart.yaml`) -- One or more **templates**, which contain Kubernetes manifest files - -_Reference: [Kubernetes Introduction to charts]_ - -## Contents of this repository - -### `binderhub` folder - -Fundamental elements of a chart including: - -- `templates` folder -- `Chart.yaml.template` -- `values.yaml` - -### `images` folder - -Docker images for applications including: - -- `binderhub` - -### `chartpress` - -Useful for compiling custom charts. - -## Usage - -In the helm-chart directory: - - chartpress - -to build the docker images and rerender the helm chart. - -[binderhub]: https://binderhub.readthedocs.io/en/latest/ -[jupyterhub]: https://jupyterhub.readthedocs.io/en/latest/ -[kubernetes]: https://kubernetes.io -[helm]: https://helm.sh/ -[helm repo]: https://github.com/kubernetes/helm -[chart]: https://helm.sh/docs/topics/charts/ -[kubernetes introduction to charts]: https://helm.sh/docs/topics/charts/ -[zero to jupyterhub with kubernetes]: https://zero-to-jupyterhub.readthedocs.io/en/latest/ diff --git a/helm-chart/binderhub/.helmignore b/helm-chart/binderhub/.helmignore index 05f3c9858..1205485fa 100644 --- a/helm-chart/binderhub/.helmignore +++ b/helm-chart/binderhub/.helmignore @@ -6,7 +6,7 @@ # # Here are files that we intentionally ignore to avoid them being packaged, # because we don't want to reference them from our templates anyhow. -schema.yaml +values.schema.yaml # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and @@ -24,8 +24,10 @@ schema.yaml *.swp *.bak *.tmp +*.orig *~ # Various IDEs .project .idea/ *.tmproj +.vscode/ diff --git a/helm-chart/binderhub/Chart.yaml b/helm-chart/binderhub/Chart.yaml index c04c1b7d4..d7c6d527a 100644 --- a/helm-chart/binderhub/Chart.yaml +++ b/helm-chart/binderhub/Chart.yaml @@ -1,34 +1,14 @@ # Chart.yaml v2 reference: https://helm.sh/docs/topics/charts/#the-chartyaml-file apiVersion: v2 name: binderhub -version: 0.0.1-set.by.chartpress -dependencies: - # Source code: https://github.com/jupyterhub/zero-to-jupyterhub-k8s - # Latest version: https://github.com/jupyterhub/zero-to-jupyterhub-k8s/tags - # App changelog: https://github.com/jupyterhub/zero-to-jupyterhub-k8s/tree/HEAD/CHANGELOG.md - # - # Important: Whenever you bump the jupyterhub Helm chart version, also inspect - # the helm-chart/images/binderhub pinned version in requirements.in - # and run "./dependencies freeze --upgrade". - # - - name: jupyterhub - version: "4.0.0" - repository: "https://jupyterhub.github.io/helm-chart" -description: |- - BinderHub is like a JupyterHub that automatically builds environments for the - users based on repo2docker. A BinderHub is by default not configured to - authenticate users or provide storage for them. -keywords: [jupyter, jupyterhub, binderhub] -home: https://binderhub.readthedocs.io/en/latest/ +version: 0.2.0 +# FIXME: appVersion should represent the version of binderhub in +# images/binderhub/requirements.txt +# +appVersion: "1.0.0" +description: A BinderHub installation separate from JupyterHub +keywords: [binderhub, repo2docker, jupyterhub, jupyter] +home: https://github.com/jupyterhub/binderhub sources: [https://github.com/jupyterhub/binderhub] -icon: https://jupyterhub.github.io/helm-chart/images/hublogo.svg -kubeVersion: ">=1.28.0-0" -maintainers: - # Since it is a requirement of Artifact Hub to have specific maintainers - # listed, we have added some below, but in practice the entire JupyterHub team - # contributes to the maintenance of this Helm chart. Please go ahead and add - # yourself! - - name: Yuvi - email: yuvipanda@gmail.com - - name: Erik Sundell - email: erik@sundellopensource.se +icon: https://binderhub.readthedocs.io/en/latest/_static/logo.png +kubeVersion: ">=1.27.0-0" diff --git a/helm-chart/binderhub/files/binderhub_config.py b/helm-chart/binderhub/files/binderhub_config.py deleted file mode 100644 index b86afa903..000000000 --- a/helm-chart/binderhub/files/binderhub_config.py +++ /dev/null @@ -1,68 +0,0 @@ -from functools import lru_cache -from urllib.parse import urlparse - -from ruamel.yaml import YAML - -yaml = YAML() - - -# memoize so we only load config once -@lru_cache -def _load_values(): - """Load configuration from disk - - Memoized to only load once - """ - path = "/etc/binderhub/config/values.yaml" - print(f"Loading {path}") - with open(path) as f: - return yaml.load(f) - - -def get_value(key, default=None): - """ - Find an item in values.yaml of a given name & return it - - get_value("a.b.c") returns values['a']['b']['c'] - """ - # start at the top - value = _load_values() - # resolve path in yaml - for level in key.split("."): - if not isinstance(value, dict): - # a parent is a scalar or null, - # can't resolve full path - return default - if level not in value: - return default - else: - value = value[level] - return value - - -# load custom templates, by default -c.BinderHub.template_path = "/etc/binderhub/templates" - -# load config from values.yaml -for section, sub_cfg in get_value("config", {}).items(): - c[section].update(sub_cfg) - -imageBuilderType = get_value("imageBuilderType") -if imageBuilderType in ["dind", "pink"]: - hostSocketDir = get_value(f"{imageBuilderType}.hostSocketDir") - if hostSocketDir: - socketname = "docker" if imageBuilderType == "dind" else "podman" - c.BinderHub.build_docker_host = f"unix://{hostSocketDir}/{socketname}.sock" - -if c.BinderHub.auth_enabled: - if "hub_url" not in c.BinderHub: - c.BinderHub.hub_url = "" - hub_url = urlparse(c.BinderHub.hub_url) - c.HubOAuth.hub_host = f"{hub_url.scheme}://{hub_url.netloc}" - if "base_url" in c.BinderHub: - c.HubOAuth.base_url = c.BinderHub.base_url - -# load extra config snippets -for key, snippet in sorted((get_value("extraConfig") or {}).items()): - print(f"Loading extra config: {key}") - exec(snippet) diff --git a/helm-chart/binderhub/mounted-files/binderhub_config.py b/helm-chart/binderhub/mounted-files/binderhub_config.py new file mode 100644 index 000000000..7cf92a980 --- /dev/null +++ b/helm-chart/binderhub/mounted-files/binderhub_config.py @@ -0,0 +1,57 @@ +""" +This configuration file is mounted to be read by binderhub with the sole purpose +of loading chart configuration passed via "config" and "extraConfig". +""" + +from functools import lru_cache + +from ruamel.yaml import YAML + + +@lru_cache +def _read_chart_config(): + """ + Read chart configuration, mounted via a k8s Secret rendered by the chart. + """ + yaml = YAML(typ="safe") + with open("/etc/binderhub/mounted-secret/chart-config.yaml") as f: + return yaml.load(f) + + +@lru_cache +def get_chart_config(config_path=None, default=None): + """ + Returns the full chart configuration, or a section of it based on a config + section's path like "config.BinderHub". + """ + config = _read_chart_config() + if not config_path: + return config + + for key in config_path.split("."): + if not isinstance(config, dict): + # can't resolve full path, + # parent section's config is is a scalar or null + return default + if key not in config: + return default + config = config[key] + return config + + +# load the config object for traitlets based configuration +c = get_config() # noqa + +# load "config" (YAML values) +for section, value in get_chart_config("config").items(): + if not value: + continue + print(f"Loading config.{section}") + c[section].update(value) + +# load "extraConfig" (Python code) +for key, snippet in sorted(get_chart_config("extraConfig").items()): + if not snippet: + continue + print(f"Running extraConfig.{key}") + exec(snippet) diff --git a/helm-chart/binderhub/schema.yaml b/helm-chart/binderhub/schema.yaml deleted file mode 100644 index 2a9587716..000000000 --- a/helm-chart/binderhub/schema.yaml +++ /dev/null @@ -1,672 +0,0 @@ -# This schema (a jsonschema in YAML format) is used to generate -# values.schema.json which is packaged with the Helm chart for client side -# validation by Helm of values before template rendering. -# -# This schema is also used by our documentation system to build the -# configuration reference section based on the description fields. See -# docs/source/conf.py for that logic! -# -# We look to document everything we have default values for in values.yaml, but -# we don't look to enforce the perfect validation logic within this file. -# -# ref: https://json-schema.org/learn/getting-started-step-by-step.html -# -$schema: http://json-schema.org/draft-07/schema# -type: object -additionalProperties: false -required: - - pdb - - replicas - - resources - - rbac - - nodeSelector - - tolerations - - image - - registry - - service - - config - - extraConfig - - jupyterhub - - deployment - - dind - - pink - - imageBuilderType - - imageCleaner - - ingress - - initContainers - - lifecycle - - extraFiles - - extraVolumes - - extraVolumeMounts - - extraEnv - - extraPodSpec - - podAnnotations - - global -properties: - pdb: &pdb-spec - type: object - additionalProperties: false - description: | - Configure a PodDisruptionBudget for this Deployment. - - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/) - for more details. - properties: - enabled: - type: boolean - description: | - Decides if a PodDisruptionBudget is created targeting the - Deployment's pods. - maxUnavailable: - type: [integer, "null"] - description: | - The maximum number of pods that can be unavailable during voluntary - disruptions. - minAvailable: - type: [integer, "null"] - description: | - The minimum number of pods required to be available during voluntary - disruptions. - - replicas: - type: integer - description: | - You can have multiple binder pods to share the workload or improve - availability on node failure. - - resources: &resources-spec - type: object - additionalProperties: true - description: | - A k8s native specification of resources, see [the - documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#resourcerequirements-v1-core). - - rbac: - type: object - additionalProperties: false - required: [enabled] - properties: - enabled: - type: boolean - description: | - Decides if RBAC resources are to be created and referenced by the the - Helm chart's workloads. - - nodeSelector: &nodeSelector-spec - type: object - additionalProperties: true - description: | - An object with key value pairs representing labels. K8s Nodes are required - to have match all these labels for this Pod to scheduled on them. - - ```yaml - disktype: ssd - nodetype: awesome - ``` - - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) - for more details. - - tolerations: &tolerations-spec - type: array - description: | - Tolerations allow a pod to be scheduled on nodes with taints. - See the - [Kubernetes docs](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) - for more info. - - image: &image-spec - type: object - additionalProperties: false - required: [name, tag] - properties: - name: - type: string - description: | - The name of the image, without the tag. - tag: - type: string - description: | - The tag of the image to pull. This is the value following `:` in - complete image specifications. - pullPolicy: - enum: [null, "", IfNotPresent, Always, Never] - description: | - Configures the Pod's `spec.imagePullPolicy`. - - See the [Kubernetes - docs](https://kubernetes.io/docs/concepts/containers/images/#updating-images) - for more info. - pullSecrets: - type: array - description: | - A list of references to existing Kubernetes Secrets with credentials - to pull the image. - - registry: - type: object - additionalProperties: false - description: | - TODO - properties: - url: - type: [string, "null"] - description: | - TODO - username: - type: [string, "null"] - description: | - TODO - password: - type: [string, "null"] - description: | - TODO - - service: - type: object - additionalProperties: false - description: | - Configuration of the Kubernetes Service resource exposing the BinderHub - web server. - properties: - type: - enum: [ClusterIP, NodePort, LoadBalancer, ExternalName] - description: | - The Kubernetes ServiceType to be used. - - The default type is `ClusterIP`. - See the [Kubernetes docs](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) - to learn more about service types. - labels: - type: object - additionalProperties: false - patternProperties: &labels-and-annotations-patternProperties - ".*": - type: string - description: | - Labels to add to the binder Service. - annotations: - type: object - additionalProperties: false - patternProperties: *labels-and-annotations-patternProperties - description: | - Annotations to add to the binder Service. - nodePort: - type: [integer, "null"] - description: | - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport) - for more details about NodePorts. - loadBalancerIP: - type: [string, "null"] - description: | - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) - for more details about loadBalancerIP. - - config: - type: object - additionalProperties: true - description: | - BinderHub and its components are Python classes that expose its - configuration through - [_traitlets_](https://traitlets.readthedocs.io/en/stable/). With this Helm - chart configuration you can directly configure the Python classes through - _static_ YAML values. To _dynamically_ set values, you need to use - `extraConfig` instead. - - __Example__ - - If you inspect documentation or some `binderhub_config.py` to contain the - following section: - - ```python - c.binderhub.some_config_option = True - ``` - - Then, you would be able to represent it with this configuration like: - - ```yaml - config: - BinderHub: - some_config_option: true - ``` - - ```{admonition} YAML limitations - :class: tip - You can't represent Python `Bytes` or `Set` objects in YAML directly. - ``` - - ```{admonition} Helm value merging - :class: tip - `helm` merges a Helm chart's default values with values passed with the - `--values` or `-f` flag. During merging, lists are replaced while - dictionaries are updated. - ``` - - extraConfig: - type: object - additionalProperties: true - description: | - Arbitrary extra python based configuration that should be in `binderhub_config.py`. - - This is the *escape hatch* - if you want to configure BinderHub to do something specific - that is not present here as an option, you can write the raw Python to do it here. - - extraConfig is a *dict*, so there can be multiple configuration snippets - under different names. - The configuration sections are run in alphabetical order. - - Since this is usually a multi-line string, you want to format it using YAML's - [| operator](https://yaml.org/spec/1.2/spec.html#id2795688). - - For example: - ```yaml - extraConfig: - my_binderhub_config.py: | - c.BinderHub.something = 'something' - ``` - - No validation of this python is performed! If you make a mistake here, it will probably - manifest as either the binder pod going into `Error` or `CrashLoopBackoff` states, or in - some special cases, the binder pod running but... just doing very random things. Be careful! - - extraFiles: - type: object - additionalProperties: false - description: | - A dictionary with extra files to be injected into the binder pod's container - on startup. This can for example be used to inject: configuration - files, custom user interface templates, images, and more. - - See zero-to-jupyterhub's extraFiles documentation for reference. - patternProperties: - ".*": - type: object - additionalProperties: false - required: [mountPath] - oneOf: - - required: [data] - - required: [stringData] - - required: [binaryData] - properties: - mountPath: - type: string - data: - type: object - additionalProperties: true - stringData: - type: string - binaryData: - type: string - mode: - type: number - - jupyterhub: - type: object - additionalProperties: true - description: | - Configuration for the JupyterHub Helm chart that is a dependency of the - BinderHub Helm chart. - required: [hub] - properties: - hub: - type: object - additionalProperties: true - required: [services] - properties: - services: - type: object - additionalProperties: true - required: [binder] - properties: - binder: - type: object - additionalProperties: true - properties: - apiToken: - type: [string, "null"] - description: | - TODO - - deployment: - type: object - additionalProperties: false - properties: - readinessProbe: &probe-spec - type: object - additionalProperties: true - description: | - This config option is exactly like the k8s native specification of a - container probe, except that it also supports an `enabled` boolean - flag. - - See [the k8s - documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#probe-v1-core) - for more details. - required: [enabled] - properties: - enabled: - type: [boolean] - livenessProbe: *probe-spec - labels: - type: object - additionalProperties: false - patternProperties: *labels-and-annotations-patternProperties - description: | - Extra labels to add to the binder pod. - - See the [Kubernetes docs](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) - to learn more about labels. - - imageBuilderType: - type: string - enum: ["host", "dind", "pink"] - default: "host" - description: | - Selected image builder type - - dind: - type: object - additionalProperties: false - properties: - enabled: - type: boolean - description: | - DEPRECATED: Use `imageBuilderType: dind` - initContainers: &initContainers-spec - type: array - description: | - List of additional initContainers. - - See the [Kubernetes - docs](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) - for more info. - daemonset: - type: object - additionalProperties: false - properties: - image: *image-spec - extraArgs: - type: array - description: | - Extra command line arguments for the Docker daemon - lifecycle: &lifecycle-spec - type: object - additionalProperties: false - description: | - A k8s native specification of lifecycle hooks on the container, see [the - documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#lifecycle-v1-core). - properties: - postStart: - type: object - additionalProperties: true - preStop: - type: object - additionalProperties: true - extraVolumes: &extraVolumes-spec - type: array - description: | - Additional volumes for the Pod. Use a k8s native syntax. - extraVolumeMounts: &extraVolumeMounts-spec - type: array - description: | - Additional volume mounts for the Container. Use a k8s native syntax. - storageDriver: - type: string - description: | - Docker storage driver - resources: *resources-spec - hostSocketDir: &hostSocketDir-spec - type: string - description: | - Host directory where the container socket will be located - hostLibDir: &hostStorageDir-spec - type: string - description: | - Host directory where the containers storage will be located - hostSocketName: &hostSocketName-spec - type: string - description: | - Name of the container socket file - - pink: - type: object - additionalProperties: false - properties: - initContainers: *initContainers-spec - daemonset: - type: object - additionalProperties: false - properties: - image: *image-spec - extraArgs: - type: array - description: | - Extra command line arguments for the Podman service, e.g. `[--log-level=debug]` - lifecycle: *lifecycle-spec - extraVolumes: *extraVolumes-spec - extraVolumeMounts: *extraVolumeMounts-spec - resources: *resources-spec - hostStorageDir: *hostStorageDir-spec - hostSocketDir: *hostSocketDir-spec - hostSocketName: *hostSocketName-spec - - imageCleaner: - type: object - additionalProperties: false - required: - - enabled - properties: - enabled: - type: boolean - description: | - TODO - image: *image-spec - cordon: - type: boolean - description: | - Whether to cordon the node while cleaning its images. - Disable, e.g. for single-node clusters. - delay: - type: integer - description: | - TODO - imageGCThresholdType: - type: string - description: | - TODO - imageGCThresholdHigh: - type: integer - description: | - TODO - imageGCThresholdLow: - type: integer - description: | - TODO - extraEnv: - type: [object, array] - additionalProperties: true - description: | - see binderhub.deployment.extraEnv - - host: - type: object - additionalProperties: false - required: [dockerSocket, dockerLibDir] - properties: - enabled: - type: boolean - description: | - DEPRECATED: use imageCleaner.enabled if the cleaner shall not used. - dockerSocket: - type: string - description: | - TODO - dockerLibDir: - type: string - description: | - TODO - - ingress: - type: object - additionalProperties: false - required: [enabled] - properties: - enabled: - type: boolean - description: | - Enable the creation of a Kubernetes Ingress referencing incoming - network network traffic to the binder k8s Service. - https: - type: object - additionalProperties: false - description: | - TODO - properties: - enabled: - type: boolean - description: | - TODO - type: - type: string - description: | - TODO - annotations: - type: object - additionalProperties: false - patternProperties: *labels-and-annotations-patternProperties - description: | - Annotations to apply to the Ingress resource. - - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) - for more details about annotations. - hosts: - type: array - description: | - List of hosts to route requests to the proxy. - ingressClassName: - type: [string, "null"] - description: | - Maps directly to the Ingress resource's spec.ingressClassName. To - configure this, your k8s cluster must have version 1.18+ or above. - - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) - for more details. - pathSuffix: - type: [string, "null"] - description: | - Suffix added to Ingress's routing path pattern. - - Specify `*` if your ingress matches path by glob pattern. - pathType: - enum: [Prefix, Exact, ImplementationSpecific] - description: | - The path type to use. The default value is 'Prefix'. Only applies on Kubernetes v1.18+. - - See [the Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types) - for more details about path types. - tls: - type: array - description: | - TLS configurations for Ingress. - - See [the Kubernetes - documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) - for more details about annotations. - - initContainers: *initContainers-spec - lifecycle: *lifecycle-spec - extraVolumes: *extraVolumes-spec - extraVolumeMounts: *extraVolumeMounts-spec - extraEnv: - type: [object, array] - additionalProperties: true - description: | - Environment variables to add to the binder pods. - - String literals with `$(ENV_VAR_NAME)` will be expanded by Kubelet which - is a part of Kubernetes. - - ```yaml - extraEnv: - # basic notation (for literal values only) - MY_ENV_VARS_NAME1: "my env var value 1" - - # explicit notation (the "name" field takes precedence) - BINDER_NAMESPACE: - name: BINDER_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - # implicit notation (the "name" field is implied) - PREFIXED_BINDER_NAMESPACE: - value: "my-prefix-$(BINDER_NAMESPACE)" - SECRET_VALUE: - valueFrom: - secretKeyRef: - name: my-k8s-secret - key: password - ``` - - For more information, see the [Kubernetes EnvVar - specification](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core). - extraPodSpec: - type: object - additionalProperties: true - description: | - Arbitrary extra k8s pod specification for the binder pod, as a YAML object. - - Use this to set extra pod fields that aren't supported by the Helm chart, e.g. - - ```yaml - extraPodSpec: - priorityClassName: my-priority-class - ``` - - See [PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec) - - podAnnotations: - type: object - additionalProperties: false - patternProperties: *labels-and-annotations-patternProperties - description: | - Annotations to add to the binder Pods. - - global: - type: object - additionalProperties: true - - hpa: - type: object - additionalProperties: true - description: | - This is not a supported configuration yet by this Helm chart, but may be - in the future. This schema entry was added because - jupyterhub/mybinder.org-deploy configured a HorizontalPodAutoscaler - resource here. - - networkPolicy: - type: object - additionalProperties: true - description: | - This is not a supported configuration yet by this Helm chart, but may be - in the future. This schema entry was added because - jupyterhub/mybinder.org-deploy configured a Networkpolicy resource here. - - # Deprecated - cors: - type: object - additionalProperties: true - description: | - DEPRECATED. - -# A placeholder as global values that can be referenced from the same location -# of any chart should be possible to provide, but aren't necessarily provided or -# used. -global: {} diff --git a/helm-chart/binderhub/templates/NOTES.txt b/helm-chart/binderhub/templates/NOTES.txt index 73da374a0..c362e806b 100644 --- a/helm-chart/binderhub/templates/NOTES.txt +++ b/helm-chart/binderhub/templates/NOTES.txt @@ -1,168 +1,45 @@ -{{- define "removedConfig" }} +{{- /* Generated with https://patorjk.com/software/taag/#p=display&h=0&f=Slant&t=BinderHub */}} +. ____ _ __ __ __ __ + / __ ) (_) ____ ____/ / ___ _____ / / / / __ __ / /_ + / __ | / / / __ \ / __ / / _ \ / ___/ / /_/ / / / / / / __ \ + / /_/ / / / / / / / / /_/ / / __/ / / / __ / / /_/ / / /_/ / +/_____/ /_/ /_/ /_/ \__,_/ \___/ /_/ /_/ /_/ \__,_/ /_.___/ -It looks like you still have some unsupported configuration! +You have successfully installed the BinderHub Helm chart! -The binderhub chart has removed special-handling -of many simple configurables in the BinderHub source code. -Instead, the Python traits configuration is exposed directly. +### Installation info -See https://binderhub.readthedocs.io/en/latest/reference/ref-index.html -for what can be configured. + - Kubernetes namespace: {{ .Release.Namespace }} + - Helm release name: {{ .Release.Name }} + - Helm chart version: {{ .Chart.Version }} + - BinderHub version: {{ .Chart.AppVersion }} + - Python packages: See https://github.com/jupyterhub/binderhub/blob/{{ include "binderhub.chart-version-to-git-ref" .Chart.Version }}/helm-chart/images/binderhub/requirements.txt -In general, it works like: +### Followup links -config: - ClassName: - trait_name: value + - Documentation: https://binderhub.readthedocs.io/en/latest/ + - Issue tracking: https://github.com/jupyterhub/binderhub/issues -e.g. to set config on BinderHub, use: +### Post-installation checklist -config: - BinderHub: - use_registry: false + - Verify that created Pods enter a Running state: -or on GitHubRepoProvider: + kubectl --namespace={{ .Release.Namespace }} get pod -config: - GitHubRepoProvider: - access_token: "..." + If a pod is stuck with a Pending or ContainerCreating status, diagnose with: -where ``BinderHub.use_registry`` and ``GitHubRepoProvider.access_token`` -are traits to be configured. + kubectl --namespace={{ .Release.Namespace }} describe pod ----------------------------------- -Specific unsupported config found: + If a pod keeps restarting, diagnose with: + kubectl --namespace={{ .Release.Namespace }} logs --previous + {{- println }} -{{- if .Values.hub }} -Special handling of hub.url is removed. Set config.BinderHub.hub_url instead: -config: - BinderHub: - hub_url: {{ .Values.hub.url }} -{{- end }} - -{{- if .Values.github }} - -Top-level github configuration is removed. -Use: - -config: - GitHubRepoProvider: - access_token: {{ .Values.github.accessToken }} - client_id: {{ .Values.github.clientId }} - client_secret: {{ .Values.github.clientSecret }} -{{- end }} - -{{- if .Values.gitlab }} - -Top-level gitlab configuration is removed. -Use: - -config: - GitLabRepoProvider: - access_token: {{ .Values.gitlab.accessToken }} - private_token: {{ .Values.gitlab.privateToken }} -{{- end }} - -{{- if ne (typeOf .Values.registry.enabled) "" }} - -registry.enabled is removed. Use: - -config: - BinderHub: - use_registry: {{ .Values.registry.enabled }} -{{- end }} - -{{- if .Values.registry.authHost }} - -registry.authHost is removed. - -Use: - - registry: - url: {{ .Values.registry.authHost }} - -to set the registry url. - -{{- if and .Values.registry.host (ne .Values.registry.host .Values.registry.authHost) }} - -If registry url and auth url differ, use: - -registry: - url: {{ .Values.registry.authHost }} -config: - DockerRegistry: - url: {{ .Values.registry.host }} - config_url: {{ .Values.registry.authHost }} - -{{- end }} -{{- end }} - -{{- if .Values.registry.tokenUrl }} - -special-handling of .registry.tokenUrl is removed. -Use: - -config: - DockerRegistry: - token_url: {{ .Values.registry.tokenUrl }} - -{{- end }} - -{{- if .Values.registry.prefix }} - -registry.prefix is removed. Use: - -config: - BinderHub: - image_prefix: {{.Values.registry.prefix }} - -{{- end }} - -{{- if or .Values.build .Values.perRepoQuota }} - -build is removed. Use: - -config: - BinderHub: - appendix: {{ .Values.build.appendix }} - build_cleanup_interval: {{ .Values.build.cleanupInterval }} - build_image: {{ .Values.build.repo2dockerImage }} - build_max_age: {{ .Values.build.maxAge }} - build_namespace: {{ .Values.build.namespace }} - build_node_selector: {{ .Values.build.nodeSelector }} - log_tail_lines: {{ .Values.build.logTailLines }} - per_repo_quota: {{ .Values.perRepoQuota }} -{{- end }} - -{{- /* end removedConfig */ -}} -{{- end }} - - -{{- if (or .Values.hub .Values.build .Values.github .Values.gitlab .Values.perRepoQuota .Values.registry.host .Values.registry.authHost .Values.registry.tokenUrl .Values.registry.prefix (ne (typeOf .Values.registry.enabled) "")) }} -{{- fail (include "removedConfig" .) }} -{{- end }} - - -1. Get the application URL by running these commands: -{{- if .Values.ingress.hostname }} - http://{{- .Values.ingress.hostname }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP:{{ .Values.service.externalPort }} -{{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "fullname" . }}" -o jsonpath="{.items[0].metadata.name}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl port-forward $POD_NAME 8080:{{ .Values.service.externalPort }} -{{- end }} +{{- /* + Breaking changes. +*/}} {{- $breaking := "" }} {{- $breaking_title := "\n" }} @@ -174,31 +51,16 @@ config: {{- $breaking_title = print $breaking_title "\n###### the `helm template` command. #####" }} {{- $breaking_title = print $breaking_title "\n#################################################################################" }} -{{- if hasKey .Values.cors "allowOrigin" }} -{{- $breaking = print $breaking "\n\nRENAMED: cors.allowOrigin has been renamed to config.BinderHub.cors_allow_origin" }} -{{- end }} - -{{- if hasKey .Values.jupyterhub.custom.cors "allowOrigin" }} -{{- $breaking = print $breaking "\n\nRENAMED: jupyterhub.custom.cors.allowOrigin has been renamed to jupyterhub.hub.config.BinderSpawner.cors_allow_origin" }} -{{- end }} -{{- if hasKey .Values.dind "enabled" }} -{{- $breaking = print $breaking "\n\nCHANGED:" }} -{{- $breaking = print $breaking "\n\ndind:" }} -{{- $breaking = print $breaking "\n enabled: true" }} -{{- $breaking = print $breaking "\n\nmust as of version 0.3.0 be replace by" }} -{{- $breaking = print $breaking "\n\nimageBuilderType: dind" }} -{{- end }} +{{- /* + This is an example (in a helm template comment) on how to detect and + communicate with regards to a breaking chart config change. -{{- if hasKey .Values.imageCleaner.host "enabled" }} -{{- $breaking = print $breaking "\n\nCHANGED:" }} -{{- $breaking = print $breaking "\n\nimageCleaner:" }} -{{- $breaking = print $breaking "\n host:" }} -{{- $breaking = print $breaking "\n enabled: true" }} -{{- $breaking = print $breaking "\n\nas of version 0.3.0 is not used anymore." }} -{{- $breaking = print $breaking "\n\nThe image cleaner is either disabled or adapted to the value of imageBuilderType." }} -{{- end }} + {{- if hasKey .Values.singleuser.cloudMetadata "enabled" }} + {{- $breaking = print $breaking "\n\nCHANGED: singleuser.cloudMetadata.enabled must as of 1.0.0 be configured using singleuser.cloudMetadata.blockWithIptables with the opposite value." }} + {{- end }} +*/}} {{- if $breaking }} -{{- fail (print $breaking_title $breaking) }} +{{- fail (print $breaking_title $breaking "\n\n") }} {{- end }} diff --git a/helm-chart/binderhub/templates/_helpers-extra-files.tpl b/helm-chart/binderhub/templates/_helpers-extra-files.tpl new file mode 100644 index 000000000..3b4262df1 --- /dev/null +++ b/helm-chart/binderhub/templates/_helpers-extra-files.tpl @@ -0,0 +1,72 @@ +{{- /* + binderhub.extraFiles.data: + Renders content for a k8s Secret's data field, coming from extraFiles with + binaryData entries. +*/}} +{{- define "binderhub.extraFiles.data.withNewLineSuffix" -}} + {{- range $file_key, $file_details := . }} + {{- include "binderhub.extraFiles.validate-file" (list $file_key $file_details) }} + {{- if $file_details.binaryData }} + {{- $file_key | quote }}: {{ $file_details.binaryData | nospace | quote }}{{ println }} + {{- end }} + {{- end }} +{{- end }} +{{- define "binderhub.extraFiles.data" -}} + {{- include "binderhub.extraFiles.data.withNewLineSuffix" . | trimSuffix "\n" }} +{{- end }} + +{{- /* + binderhub.extraFiles.stringData: + Renders content for a k8s Secret's stringData field, coming from extraFiles + with either data or stringData entries. +*/}} +{{- define "binderhub.extraFiles.stringData.withNewLineSuffix" -}} + {{- range $file_key, $file_details := . }} + {{- include "binderhub.extraFiles.validate-file" (list $file_key $file_details) }} + {{- $file_name := $file_details.mountPath | base }} + {{- if $file_details.stringData }} + {{- $file_key | quote }}: | + {{- $file_details.stringData | trimSuffix "\n" | nindent 2 }}{{ println }} + {{- end }} + {{- if $file_details.data }} + {{- $file_key | quote }}: | + {{- if or (eq (ext $file_name) ".yaml") (eq (ext $file_name) ".yml") }} + {{- $file_details.data | toYaml | nindent 2 }}{{ println }} + {{- else if eq (ext $file_name) ".json" }} + {{- $file_details.data | toJson | nindent 2 }}{{ println }} + {{- else if eq (ext $file_name) ".toml" }} + {{- $file_details.data | toToml | trimSuffix "\n" | nindent 2 }}{{ println }} + {{- else }} + {{- print "\n\nextraFiles entries with 'data' (" $file_key " > " $file_details.mountPath ") needs to have a filename extension of .yaml, .yml, .json, or .toml!" | fail }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- define "binderhub.extraFiles.stringData" -}} + {{- include "binderhub.extraFiles.stringData.withNewLineSuffix" . | trimSuffix "\n" }} +{{- end }} + +{{- define "binderhub.extraFiles.validate-file" -}} + {{- $file_key := index . 0 }} + {{- $file_details := index . 1 }} + + {{- /* Use of mountPath. */}} + {{- if not ($file_details.mountPath) }} + {{- print "\n\nextraFiles entries (" $file_key ") must contain the field 'mountPath'." | fail }} + {{- end }} + + {{- /* Use one of stringData, binaryData, data. */}} + {{- $field_count := 0 }} + {{- if $file_details.data }} + {{- $field_count = add1 $field_count }} + {{- end }} + {{- if $file_details.stringData }} + {{- $field_count = add1 $field_count }} + {{- end }} + {{- if $file_details.binaryData }} + {{- $field_count = add1 $field_count }} + {{- end }} + {{- if ne $field_count 1 }} + {{- print "\n\nextraFiles entries (" $file_key ") must only contain one of the fields: 'data', 'stringData', and 'binaryData'." | fail }} + {{- end }} +{{- end }} diff --git a/helm-chart/binderhub/templates/_helpers-labels.tpl b/helm-chart/binderhub/templates/_helpers-labels.tpl new file mode 100644 index 000000000..805bfe5ce --- /dev/null +++ b/helm-chart/binderhub/templates/_helpers-labels.tpl @@ -0,0 +1,19 @@ +{{- /* + Common labels +*/}} +{{- define "binderhub.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{ include "binderhub.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- /* + Selector labels +*/}} +{{- define "binderhub.selectorLabels" -}} +app.kubernetes.io/name: {{ .Values.nameOverride | default .Chart.Name | trunc 63 | trimSuffix "-" }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/helm-chart/binderhub/templates/_helpers-names.tpl b/helm-chart/binderhub/templates/_helpers-names.tpl new file mode 100644 index 000000000..ebc0d0fe0 --- /dev/null +++ b/helm-chart/binderhub/templates/_helpers-names.tpl @@ -0,0 +1,119 @@ +{{- /* + These helpers encapsulates logic on how we name resources. They also enable + parent charts to reference these dynamic resource names. + + To avoid duplicating documentation, for more information, please see the the + fullnameOverride entry the jupyterhub chart's configuration reference: + https://z2jh.jupyter.org/en/latest/resources/reference.html#fullnameOverride +*/}} + + + +{{- /* + Utility templates +*/}} + +{{- /* + Renders to a prefix for the chart's resource names. This prefix is assumed to + make the resource name cluster unique. +*/}} +{{- define "binderhub.fullname" -}} + {{- /* + We have implemented a trick to allow a parent chart depending on this + chart to call these named templates. + + Caveats and notes: + + 1. While parent charts can reference these, grandparent charts can't. + 2. Parent charts must not use an alias for this chart. + 3. There is no failsafe workaround to above due to + https://github.com/helm/helm/issues/9214. + 4. .Chart is of its own type (*chart.Metadata) and needs to be casted + using "toYaml | fromYaml" in order to be able to use normal helm + template functions on it. + */}} + {{- $fullname_override := .Values.fullnameOverride }} + {{- $name_override := .Values.nameOverride }} + {{- if ne .Chart.Name "binderhub" }} + {{- if .Values.jupyterhub }} + {{- $fullname_override = .Values.jupyterhub.fullnameOverride }} + {{- $name_override = .Values.jupyterhub.nameOverride }} + {{- end }} + {{- end }} + + {{- if eq (typeOf $fullname_override) "string" }} + {{- $fullname_override }} + {{- else }} + {{- $name := $name_override | default .Chart.Name }} + {{- if contains $name .Release.Name }} + {{- .Release.Name }} + {{- else }} + {{- .Release.Name }}-{{ $name }} + {{- end }} + {{- end }} +{{- end }} + +{{- /* + Renders to a blank string or if the fullname template is truthy renders to it + with an appended dash. +*/}} +{{- define "binderhub.fullname.dash" -}} + {{- if (include "binderhub.fullname" .) }} + {{- include "binderhub.fullname" . }}- + {{- end }} +{{- end }} + + + +{{- /* + Namespaced resources +*/}} + +{{- /* binderhub resources' default name */}} +{{- define "binderhub.binderhub.fullname" -}} + {{- include "binderhub.fullname.dash" . }}binderhub +{{- end }} + +{{- /* binderhub's ServiceAccount name */}} +{{- define "binderhub.binderhub.serviceaccount.fullname" -}} + {{- if .Values.serviceAccount.create }} + {{- .Values.serviceAccount.name | default (include "binderhub.binderhub.fullname" .) }} + {{- else }} + {{- .Values.serviceAccount.name }} + {{- end }} +{{- end }} + +{{- /* binderhub's Ingress name */}} +{{- define "binderhub.binderhub.ingress.fullname" -}} + {{- if (include "binderhub.fullname" .) }} + {{- include "binderhub.fullname" . }} + {{- else -}} + binderhub + {{- end }} +{{- end }} + +{{- /* docker-api resources' default name */}} +{{- define "binderhub.docker-api.fullname" -}} + {{- include "binderhub.fullname.dash" . }}docker-api +{{- end }} + +{{- /* build-pods-docker-config name */}} +{{- define "binderhub.build-pods-docker-config.fullname" -}} + {{- include "binderhub.fullname.dash" . }}build-pods-docker-config +{{- end }} + + + +{{- /* + Cluster wide resources + + We enforce uniqueness of names for our cluster wide resources. We assume that + the prefix from setting fullnameOverride to null or a string will be cluster + unique. +*/}} + +{{- /* + We currently have no cluster wide resources, but if you add one below in the + future, remove this comment and add an entry mimicing how the jupyterhub helm + chart does it. +*/}} diff --git a/helm-chart/binderhub/templates/_helpers.tpl b/helm-chart/binderhub/templates/_helpers.tpl index 39780deb0..bc1584d58 100644 --- a/helm-chart/binderhub/templates/_helpers.tpl +++ b/helm-chart/binderhub/templates/_helpers.tpl @@ -1,48 +1,18 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} +{{- /* + binderhub.chart-version-to-git-ref: + Renders a valid git reference from a chartpress generated version string. + In practice, either a git tag or a git commit hash will be returned. -{{/* -Render docker config.json for the registry-publishing secret and other docker configuration. -*/}} -{{- define "buildDockerConfig" -}} - -{{- /* default auth url */ -}} -{{- $url := (default "https://index.docker.io/v1" .Values.registry.url) }} + - The version string will follow a chartpress pattern, + like "0.1.0-0.dev.git.17.h8368bc0", see + https://github.com/jupyterhub/chartpress#examples-chart-versions-and-image-tags. -{{- /* default username if unspecified - (_json_key for gcr.io, otherwise) -*/ -}} + - The regexReplaceAll function is a sprig library function, see + https://masterminds.github.io/sprig/strings.html. -{{- if not .Values.registry.username }} - {{- if eq $url "https://gcr.io" }} - {{- $_ := set .Values.registry "username" "_json_key" }} - {{- else }} - {{- $_ := set .Values.registry "username" "" }} - {{- end }} -{{- end }} -{{- $username := .Values.registry.username -}} - -{{- /* initialize a dict to represent a docker config with registry credentials */}} -{{- $dockerConfig := dict "auths" (dict $url (dict "auth" (printf "%s:%s" $username .Values.registry.password | b64enc))) }} - -{{- /* augment our initialized docker config with buildDockerConfig */}} -{{- if .Values.config.BinderHub.buildDockerConfig }} -{{- $dockerConfig := merge $dockerConfig .Values.config.BinderHub.buildDockerConfig }} -{{- end }} - -{{- $dockerConfig | toPrettyJson }} + - The regular expression is in golang syntax, but \d had to become \\d for + example. +*/}} +{{- define "binderhub.chart-version-to-git-ref" -}} +{{- regexReplaceAll ".*\\.git\\.\\d+\\.h(.*)" . "${1}" }} {{- end }} diff --git a/helm-chart/binderhub/templates/build-pods-docker-config/secret.yaml b/helm-chart/binderhub/templates/build-pods-docker-config/secret.yaml new file mode 100644 index 000000000..2bdc4f90f --- /dev/null +++ b/helm-chart/binderhub/templates/build-pods-docker-config/secret.yaml @@ -0,0 +1,50 @@ +# This Secret is mounted by BinderHub's managed build pods because +# c.KubernetesBuildExecutor.push_secret is configured with this Secret's name. +# +# IMPORTANT: This is _not_ a Kubernetes imagePullSecrets formatted Secret, it +# instead provides a config file for a docker client. +# +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "binderhub.build-pods-docker-config.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} +type: Opaque +stringData: + # config.json refers to docker config that should house credentials for the + # docker client in a build pod to use against the docker-api. + # + # Docker's config.json expects something like below, where the xx...xx= string + # is ":" base64 encoded. + # + # { + # "auths": { + # "https://index.docker.io/v1/": { + # "auth": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=" + # } + # } + # } + # + # ref: https://github.com/jupyterhub/binderhub/blob/79c5f61a992010f108637e3c434d9e606a3c8f72/binderhub/build.py#L397-L406 + # + {{- /* initialize a dict to represent a docker client config */}} + {{- $dockerConfig := dict }} + + {{- $server := .Values.buildPodsRegistryCredentials.server }} + {{- $username := .Values.buildPodsRegistryCredentials.username }} + {{- $password := .Values.buildPodsRegistryCredentials.password }} + {{- $blob := printf "%s:%s" $username $password | b64enc }} + {{- $credentials := dict "auths" (dict $server (dict "auth" $blob)) }} + + {{- /* merge docker client config with registry credentials */}} + {{- if .Values.config.BinderHub.use_registry }} + {{- $dockerConfig = merge $dockerConfig $credentials }} + {{- end }} + + {{- /* merge docker client config of any kind */}} + {{- if .Values.buildPodsDockerConfig }} + {{- $dockerConfig = merge $dockerConfig .Values.buildPodsDockerConfig }} + {{- end }} + config.json: | + {{- $dockerConfig | toPrettyJson | nindent 4 }} diff --git a/helm-chart/binderhub/templates/container-builder/daemonset.yaml b/helm-chart/binderhub/templates/container-builder/daemonset.yaml deleted file mode 100644 index 427c20e8d..000000000 --- a/helm-chart/binderhub/templates/container-builder/daemonset.yaml +++ /dev/null @@ -1,148 +0,0 @@ -{{- if ne .Values.imageBuilderType "host" -}} -{{- $builderName := .Values.imageBuilderType -}} -{{- $builder := index .Values $builderName -}} -{{- $daemonset := $builder.daemonset -}} -{{- $hostSocketPath := printf "%s/%s" $builder.hostSocketDir $builder.hostSocketName }} - -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: {{ .Release.Name }}-{{ $builderName }} -spec: - updateStrategy: - type: RollingUpdate - selector: - matchLabels: - name: {{ .Release.Name }}-{{ $builderName }} - template: - metadata: - labels: - name: {{ .Release.Name }}-{{ $builderName }} - app: binder - component: image-builder - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - spec: - {{- with include "jupyterhub.imagePullSecrets" (dict "root" . "image" $daemonset.image) }} - imagePullSecrets: {{ . }} - {{- end }} - tolerations: - - effect: NoSchedule - key: hub.jupyter.org/dedicated - operator: Equal - value: user - - effect: NoSchedule - key: hub.jupyter.org_dedicated - operator: Equal - value: user - nodeSelector: {{ .Values.config.BinderHub.build_node_selector | toJson }} - - initContainers: - - name: filesystem - # Reuse the main container image since this is a simple shell command - image: {{ $daemonset.image.name }}:{{ $daemonset.image.tag }} - {{- with $daemonset.image.pullPolicy }} - imagePullPolicy: {{ . }} - {{- end }} - command: - - sh - - -c - - > - if [ -d "{{ $hostSocketPath }}" ]; then - echo "Removing incorrect socket directory {{ $hostSocketPath }}"; - rmdir "{{ $hostSocketPath }}"; - fi - securityContext: - privileged: true - volumeMounts: - - name: run-{{ $builderName }} - mountPath: {{ $builder.hostSocketDir }} - {{- with $builder.initContainers }} - {{- . | toYaml | nindent 8 }} - {{- end }} - - containers: - - name: {{ $builderName }} - image: {{ $daemonset.image.name }}:{{ $daemonset.image.tag }} - {{- with $daemonset.image.pullPolicy }} - imagePullPolicy: {{ . }} - {{- end }} - {{- with $builder.resources }} - resources: - {{- $builder.resources | toYaml | nindent 12 }} - {{- end }} - {{- if eq $builderName "dind" }} - args: - - dockerd - - --storage-driver={{ $builder.storageDriver }} - - -H unix://{{ $hostSocketPath }} - {{- with $daemonset.extraArgs }} - {{- . | toYaml | nindent 12 }} - {{- end }} - securityContext: - privileged: true - volumeMounts: - - name: dockerlib-dind - mountPath: /var/lib/docker - - name: run-dind - mountPath: {{ $builder.hostSocketDir }} - {{- end }} - {{- if eq $builderName "pink" }} - args: - - podman - - system - - service - - --time=0 - - unix://{{ $hostSocketPath }} - {{- with $daemonset.extraArgs }} - {{- . | toYaml | nindent 12 }} - {{- end }} - securityContext: - privileged: true - runAsUser: 0 - volumeMounts: - - name: podman-containers - mountPath: /var/lib/containers/storage - - name: run-pink - mountPath: {{ $builder.hostSocketDir }} - {{- end }} - - {{- with $daemonset.extraVolumeMounts }} - {{- . | toYaml | nindent 10 }} - {{- end }} - - {{- with $daemonset.lifecycle }} - lifecycle: - {{- . | toYaml | nindent 12 }} - {{- end }} - - volumes: - {{- if eq $builderName "dind" }} - - name: dockerlib-dind - hostPath: - path: {{ $builder.hostLibDir }} - type: DirectoryOrCreate - - name: run-dind - hostPath: - path: {{ $builder.hostSocketDir }} - type: DirectoryOrCreate - {{- with $daemonset.extraVolumes }} - {{- . | toYaml | nindent 8 }} - {{- end }} - {{- end }} - {{- if eq $builderName "pink" }} - - name: podman-containers - hostPath: - path: {{ $builder.hostStorageDir }} - type: DirectoryOrCreate - - name: run-pink - hostPath: - path: {{ $builder.hostSocketDir }} - type: DirectoryOrCreate - {{- with $daemonset.extraVolumes }} - {{- . | toYaml | nindent 8 }} - {{- end }} - {{- end }} - - terminationGracePeriodSeconds: 30 -{{- end }} diff --git a/helm-chart/binderhub/templates/deployment.yaml b/helm-chart/binderhub/templates/deployment.yaml index 42237df36..32a674083 100644 --- a/helm-chart/binderhub/templates/deployment.yaml +++ b/helm-chart/binderhub/templates/deployment.yaml @@ -1,173 +1,101 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: binder + name: {{ include "binderhub.binderhub.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicas }} selector: matchLabels: - app: binder - component: binder - release: {{ .Release.Name }} - strategy: - rollingUpdate: - {{- if eq (.Values.replicas | int) 1 }} - maxSurge: 1 - maxUnavailable: 0 - {{- end }} + {{- include "binderhub.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: binderhub template: metadata: - labels: - app: binder - name: binder - component: binder - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - {{- with .Values.deployment.labels }} - {{- . | toYaml | nindent 8 }} - {{- end }} annotations: - # This lets us autorestart when the secret's pass-through config changes - checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + checksum/mounted-secret: {{ include (print .Template.BasePath "/secret.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- . | toYaml | nindent 8 }} {{- end }} + labels: + {{- include "binderhub.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: binderhub spec: - {{- with include "jupyterhub.imagePullSecrets" (dict "root" . "image" .Values.image) }} - imagePullSecrets: {{ . }} - {{- end }} - {{- with .Values.initContainers }} - initContainers: - {{- . | toYaml | nindent 8 }} - {{- end }} - nodeSelector: {{ .Values.nodeSelector | toJson }} - {{- with .Values.tolerations }} - tolerations: + volumes: + - name: secret + secret: + secretName: {{ include "binderhub.binderhub.fullname" . }} + {{- with .Values.extraVolumes }} + {{- tpl (. | toYaml) $ | nindent 8 }} + {{- end }} + containers: + - name: binderhub + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + {{- with .Values.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.config.BinderHub.port }} + volumeMounts: + - name: secret + mountPath: /etc/binderhub/mounted-secret/ + readOnly: true + {{- with .Values.extraVolumeMounts }} + {{- tpl (. | toYaml) $ | nindent 12 }} + {{- end }} + env: + - name: PUSH_SECRET_NAME + value: {{ include "binderhub.build-pods-docker-config.fullname" . }} + - name: HELM_RELEASE_NAME + value: {{ .Release.Name }} + # Namespace build pods should be placed in + - name: BUILD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + # Namespace the binderhub pod is running in + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.extraCredentials.googleServiceAccountKey }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /etc/binderhub/mounted-secret/gcp-sa-key.json + {{- end }} + {{- with .Values.extraEnv }} + {{- tpl (. | toYaml) $ | nindent 12 }} + {{- end }} + resources: + {{- .Values.resources | toYaml | nindent 12 }} + securityContext: + {{- .Values.securityContext | toYaml | nindent 12 }} + startupProbe: + periodSeconds: 1 + failureThreshold: 60 + httpGet: + path: {{ .Values.config.BinderHub.base_url | trimSuffix "/" }}/versions + port: http + {{- with .Values.image.pullSecrets }} + imagePullSecrets: {{- . | toYaml | nindent 8 }} {{- end }} - {{- if .Values.rbac.enabled }} - serviceAccountName: binderhub + {{- with include "binderhub.binderhub.serviceaccount.fullname" . }} + serviceAccountName: {{ . }} {{- end }} - volumes: - - name: config - secret: - secretName: binder-secret - {{- if .Values.extraFiles }} - - name: files - secret: - secretName: binder-secret - items: - {{- range $file_key, $file_details := .Values.extraFiles }} - - key: {{ $file_key | quote }} - path: {{ $file_key | quote }} - {{- with $file_details.mode }} - mode: {{ . }} - {{- end }} - {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- . | toYaml | nindent 8 }} {{- end }} - {{- if .Values.config.BinderHub.use_registry }} - - name: docker-secret - secret: - secretName: binder-build-docker-config - {{- else }} - - name: docker-socket - hostPath: - path: /var/run/docker.sock + {{- with .Values.nodeSelector }} + nodeSelector: + {{- . | toYaml | nindent 8 }} {{- end }} - - {{- with .Values.extraVolumes }} - {{- . | toYaml | nindent 6 }} + {{- with .Values.affinity }} + affinity: + {{- . | toYaml | nindent 8 }} {{- end }} - containers: - - name: binder - image: {{ .Values.image.name }}:{{ .Values.image.tag }} - args: - - --config - - /etc/binderhub/config/binderhub_config.py - {{- with .Values.lifecycle }} - lifecycle: - {{- . | toYaml | nindent 10 }} - {{- end }} - volumeMounts: - - mountPath: /etc/binderhub/config/ - name: config - readOnly: true - {{- range $file_key, $file_details := .Values.extraFiles }} - - mountPath: {{ $file_details.mountPath }} - subPath: {{ $file_key | quote }} - {{- with $file_details.mode }} - mode: {{ . }} - {{- end }} - name: files - {{- end }} - {{- if .Values.config.BinderHub.use_registry }} - - mountPath: /root/.docker - name: docker-secret - readOnly: true - {{- else }} - - mountPath: /var/run/docker.sock - name: docker-socket - {{- end }} - {{- with .Values.extraVolumeMounts }} - {{- . | toYaml | nindent 10 }} - {{- end }} - resources: - {{- .Values.resources | toYaml | nindent 10 }} - imagePullPolicy: IfNotPresent - env: - - name: BUILD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: JUPYTERHUB_API_TOKEN - valueFrom: - secretKeyRef: - name: "{{ include "jupyterhub.hub.fullname" . }}" - key: hub.services.binder.apiToken - {{- if .Values.config.BinderHub.auth_enabled }} - - name: JUPYTERHUB_SERVICE_NAME - value: binder - - name: JUPYTERHUB_API_URL - value: {{ (print (.Values.config.BinderHub.hub_url_local | default .Values.config.BinderHub.hub_url | trimSuffix "/") "/hub/api/") }} - - name: JUPYTERHUB_BASE_URL - value: {{ .Values.jupyterhub.hub.baseUrl | quote }} - - name: JUPYTERHUB_CLIENT_ID - value: {{ .Values.jupyterhub.hub.services.binder.oauth_client_id | quote }} - - name: JUPYTERHUB_OAUTH_CALLBACK_URL - value: {{ .Values.jupyterhub.hub.services.binder.oauth_redirect_uri | quote }} - {{- if .Values.jupyterhub.hub.allowNamedServers }} - - name: JUPYTERHUB_ALLOW_NAMED_SERVERS - value: "true" - - name: JUPYTERHUB_NAMED_SERVER_LIMIT_PER_USER - value: {{ .Values.jupyterhub.hub.namedServerLimitPerUser | quote }} - {{- end }} - {{- end }} - {{- with .Values.extraEnv }} - {{- include "jupyterhub.extraEnv" . | nindent 8 }} - {{- end }} - ports: - - containerPort: 8585 - name: binder - {{- if .Values.deployment.readinessProbe.enabled }} - readinessProbe: - httpGet: - path: {{ .Values.config.BinderHub.base_url }}versions - port: binder - initialDelaySeconds: {{ .Values.deployment.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.deployment.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.deployment.readinessProbe.timeoutSeconds }} - failureThreshold: {{ .Values.deployment.readinessProbe.failureThreshold }} - {{- end }} - {{- if .Values.deployment.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: {{ .Values.config.BinderHub.base_url }}versions - port: binder - initialDelaySeconds: {{ .Values.deployment.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.deployment.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.deployment.livenessProbe.timeoutSeconds }} - failureThreshold: {{ .Values.deployment.livenessProbe.failureThreshold }} - {{- end }} - {{- with .Values.extraPodSpec }} - {{- toYaml . | nindent 6 }} + {{- with .Values.tolerations }} + tolerations: + {{- . | toYaml | nindent 8 }} {{- end }} diff --git a/helm-chart/binderhub/templates/docker-api/daemonset.yaml b/helm-chart/binderhub/templates/docker-api/daemonset.yaml new file mode 100644 index 000000000..91e7dfd53 --- /dev/null +++ b/helm-chart/binderhub/templates/docker-api/daemonset.yaml @@ -0,0 +1,89 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ include "binderhub.docker-api.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} + app.kubernetes.io/component: docker-api +spec: + selector: + matchLabels: + {{- include "binderhub.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: docker-api + template: + metadata: + labels: + {{- include "binderhub.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: docker-api + {{- with .Values.podAnnotations }} + annotations: + {{- . | toYaml | nindent 8 }} + {{- end }} + spec: + containers: + - name: docker-api + image: {{ .Values.dockerApi.image.repository }}:{{ .Values.dockerApi.image.tag }} + args: + - dockerd + - --data-root=/var/lib/{{ .Release.Namespace }}-{{ .Release.Name }}/docker-api + - --exec-root=/var/run/{{ .Release.Namespace }}-{{ .Release.Name }}/docker-api + - --host=unix:///var/run/{{ .Release.Namespace}}-{{ .Release.Name }}/docker-api/docker-api.sock + {{- with .Values.dockerApi.extraArgs }} + {{- . | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /var/lib/{{ .Release.Namespace }}-{{ .Release.Name }}/docker-api + - name: exec + mountPath: /var/run/{{ .Release.Namespace }}-{{ .Release.Name }}/docker-api + {{- range $file_key, $file_details := .Values.dockerApi.extraFiles }} + - name: files + mountPath: {{ $file_details.mountPath }} + subPath: {{ $file_key | quote }} + {{- end }} + resources: + {{- .Values.dockerApi.resources | toYaml | nindent 12 }} + securityContext: + {{- .Values.dockerApi.securityContext | toYaml | nindent 12 }} + volumes: + - name: data + hostPath: + path: /var/lib/{{ .Release.Namespace }}-{{ .Release.Name }}/docker-api + type: DirectoryOrCreate + - name: exec + hostPath: + path: /var/run/{{ .Release.Namespace}}-{{ .Release.Name }}/docker-api + type: DirectoryOrCreate + {{- if .Values.dockerApi.extraFiles }} + - name: files + secret: + secretName: {{ include "binderhub.docker-api.fullname" . }} + items: + {{- range $file_key, $file_details := .Values.dockerApi.extraFiles }} + - key: {{ $file_key | quote }} + path: {{ $file_key | quote }} + {{- with $file_details.mode }} + mode: {{ . }} + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.dockerApi.image.pullSecrets }} + imagePullSecrets: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with .Values.dockerApi.podSecurityContext }} + securityContext: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with .Values.dockerApi.nodeSelector }} + nodeSelector: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with .Values.dockerApi.affinity }} + affinity: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with .Values.dockerApi.tolerations }} + tolerations: + {{- . | toYaml | nindent 8 }} + {{- end }} diff --git a/helm-chart/binderhub/templates/docker-api/secret.yaml b/helm-chart/binderhub/templates/docker-api/secret.yaml new file mode 100644 index 000000000..38570fc7d --- /dev/null +++ b/helm-chart/binderhub/templates/docker-api/secret.yaml @@ -0,0 +1,17 @@ +{{- if .Values.dockerApi.extraFiles }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ include "binderhub.docker-api.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} +type: Opaque +{{- with include "binderhub.extraFiles.data" .Values.dockerApi.extraFiles }} +data: + {{- . | nindent 2 }} +{{- end }} +{{- with include "binderhub.extraFiles.stringData" .Values.dockerApi.extraFiles }} +stringData: + {{- . | nindent 2 }} +{{- end }} +{{- end }} diff --git a/helm-chart/binderhub/templates/image-cleaner.yaml b/helm-chart/binderhub/templates/image-cleaner.yaml deleted file mode 100644 index 5497557e8..000000000 --- a/helm-chart/binderhub/templates/image-cleaner.yaml +++ /dev/null @@ -1,92 +0,0 @@ -{{- if .Values.imageCleaner.enabled -}} -{{- $builderName := .Values.imageBuilderType -}} -{{- $builder := index .Values $builderName -}} - -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: {{ .Release.Name }}-image-cleaner -spec: - updateStrategy: - type: RollingUpdate - selector: - matchLabels: - name: {{ .Release.Name }}-image-cleaner - template: - metadata: - labels: - name: {{ .Release.Name }}-image-cleaner - app: binder - component: image-cleaner - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} - spec: - {{- with include "jupyterhub.imagePullSecrets" (dict "root" . "image" .Values.imageCleaner.image) }} - imagePullSecrets: {{ . }} - {{- end }} - tolerations: - - effect: NoSchedule - key: hub.jupyter.org/dedicated - operator: Equal - value: user - - effect: NoSchedule - key: hub.jupyter.org_dedicated - operator: Equal - value: user - nodeSelector: {{ .Values.config.BinderHub.build_node_selector | toJson }} - {{- if .Values.rbac.enabled }} - serviceAccountName: {{ .Release.Name }}-image-cleaner - {{- end }} - containers: - - name: image-cleaner-{{ $builderName }} - image: {{ .Values.imageCleaner.image.name }}:{{ .Values.imageCleaner.image.tag }} - {{- with .Values.imageCleaner.image.pullPolicy }} - imagePullPolicy: {{ . }} - {{- end }} - volumeMounts: - - name: storage-{{ $builderName }} - mountPath: /var/lib/{{ $builderName }} - - name: socket-{{ $builderName }} - mountPath: /var/run/docker.sock - env: - {{- if .Values.imageCleaner.cordon }} - - name: DOCKER_IMAGE_CLEANER_NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - {{- end }} - - name: DOCKER_IMAGE_CLEANER_PATH_TO_CHECK - value: /var/lib/{{ $builderName }} - - name: DOCKER_IMAGE_CLEANER_DELAY_SECONDS - value: {{ .Values.imageCleaner.delay | quote }} - - name: DOCKER_IMAGE_CLEANER_THRESHOLD_TYPE - value: {{ .Values.imageCleaner.imageGCThresholdType | quote }} - - name: DOCKER_IMAGE_CLEANER_THRESHOLD_HIGH - value: {{ .Values.imageCleaner.imageGCThresholdHigh | quote }} - - name: DOCKER_IMAGE_CLEANER_THRESHOLD_LOW - value: {{ .Values.imageCleaner.imageGCThresholdLow | quote }} - {{- with .Values.imageCleaner.extraEnv }} - {{- include "jupyterhub.extraEnv" . | nindent 8 }} - {{- end }} - terminationGracePeriodSeconds: 0 - volumes: - {{- if eq $builderName "host" }} - - name: storage-host - hostPath: - path: {{ .Values.imageCleaner.host.dockerLibDir }} - - name: socket-host - hostPath: - path: {{ .Values.imageCleaner.host.dockerSocket }} - type: Socket - {{- end }} - {{- if or (eq $builderName "dind") (eq $builderName "pink") }} - - name: storage-{{ $builderName }} - hostPath: - path: {{ eq $builderName "dind" | ternary $builder.hostLibDir $builder.hostStorageDir }} - type: DirectoryOrCreate - - name: socket-{{ $builderName }} - hostPath: - path: {{ $builder.hostSocketDir }}/{{ $builder.hostSocketName }} - type: Socket - {{- end }} -{{- end }} diff --git a/helm-chart/binderhub/templates/ingress.yaml b/helm-chart/binderhub/templates/ingress.yaml index f8511e831..5126b3d1f 100644 --- a/helm-chart/binderhub/templates/ingress.yaml +++ b/helm-chart/binderhub/templates/ingress.yaml @@ -2,50 +2,34 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: binderhub - {{- if or (and .Values.ingress.https.enabled (eq .Values.ingress.https.type "kube-lego")) .Values.ingress.annotations }} + name: {{ include "binderhub.binderhub.ingress.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} annotations: - {{- if and .Values.ingress.https.enabled (eq .Values.ingress.https.type "kube-lego") }} - kubernetes.io/tls-acme: "true" - {{- end }} - {{- with .Values.ingress.annotations }} {{- . | toYaml | nindent 4 }} - {{- end }} {{- end }} spec: {{- with .Values.ingress.ingressClassName }} - ingressClassName: {{ . | quote }} + ingressClassName: "{{ . }}" {{- end }} rules: {{- range $host := .Values.ingress.hosts | default (list "") }} - http: paths: - - path: /{{ $.Values.ingress.pathSuffix }} + - path: {{ $.Values.config.BinderHub.base_url | trimSuffix "/" }}/{{ $.Values.ingress.pathSuffix }} pathType: {{ $.Values.ingress.pathType }} backend: service: - name: binder + name: {{ include "binderhub.binderhub.fullname" $ }} port: - number: 80 - {{- with $host }} - host: {{ . | quote }} + name: http + {{- if $host }} + host: {{ $host | quote }} {{- end }} {{- end }} - {{- if and .Values.ingress.https.enabled (eq .Values.ingress.https.type "kube-lego") }} - tls: - - secretName: kubelego-tls-binder-{{ .Release.Name }} - hosts: - {{- range .Values.ingress.hosts }} - - {{ . | quote }} - {{- end }} - {{- else if .Values.ingress.tls }} + {{- with .Values.ingress.tls }} tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} + {{- . | toYaml | nindent 4 }} {{- end }} {{- end }} diff --git a/helm-chart/binderhub/templates/pdb.yaml b/helm-chart/binderhub/templates/pdb.yaml deleted file mode 100644 index c83949e51..000000000 --- a/helm-chart/binderhub/templates/pdb.yaml +++ /dev/null @@ -1,29 +0,0 @@ -{{- if .Values.pdb.enabled -}} -{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} -apiVersion: policy/v1 -{{- else }} -apiVersion: policy/v1beta1 -{{- end }} -kind: PodDisruptionBudget -metadata: - name: binderhub - labels: - app: binder - name: binder - component: binder - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - {{- if not (.Values.pdb.maxUnavailable | typeIs "") }} - maxUnavailable: {{ .Values.pdb.maxUnavailable }} - {{- end }} - {{- if not (.Values.pdb.minAvailable | typeIs "") }} - minAvailable: {{ .Values.pdb.minAvailable }} - {{- end }} - selector: - matchLabels: - app: binder - name: binder - component: binder - release: {{ .Release.Name }} -{{- end }} diff --git a/helm-chart/binderhub/templates/rbac.yaml b/helm-chart/binderhub/templates/rbac.yaml deleted file mode 100644 index 5716b5abf..000000000 --- a/helm-chart/binderhub/templates/rbac.yaml +++ /dev/null @@ -1,91 +0,0 @@ -{{- if .Values.rbac.enabled -}} -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - labels: - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: binderhub -rules: -- apiGroups: [""] # "" indicates the core API group - resources: ["pods"] - verbs: ["get", "watch", "list", "create", "delete"] -- apiGroups: [""] - resources: ["pods/log"] - verbs: ["get"] ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - labels: - app: binderhub - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: binderhub -subjects: -- kind: ServiceAccount - namespace: {{ .Release.Namespace }} - name: binderhub -roleRef: - kind: Role - name: binderhub - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app: binderhub - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: binderhub -{{- if .Values.imageCleaner.enabled }} ---- -# image-cleaner role -# needs to cordon nodes during image cleaning -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - labels: - app: binderhub - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: {{ .Release.Name }}-image-cleaner -rules: -- apiGroups: [""] # "" indicates the core API group - resources: ["nodes"] - verbs: ["get", "patch"] ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - labels: - app: binderhub - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: {{ .Release.Name }}-image-cleaner -subjects: -- kind: ServiceAccount - namespace: {{ .Release.Namespace }} - name: {{ .Release.Name }}-image-cleaner -roleRef: - kind: ClusterRole - name: {{ .Release.Name }}-image-cleaner - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app: binderhub - chart: {{ .Chart.Name }}-{{ .Chart.Version }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - name: {{ .Release.Name }}-image-cleaner -{{- end }} -{{- end }} diff --git a/helm-chart/binderhub/templates/role.yaml b/helm-chart/binderhub/templates/role.yaml new file mode 100644 index 000000000..b379c4fae --- /dev/null +++ b/helm-chart/binderhub/templates/role.yaml @@ -0,0 +1,15 @@ +{{- if .Values.rbac.create -}} +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "binderhub.binderhub.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list", "create", "delete"] + - apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] +{{- end }} diff --git a/helm-chart/binderhub/templates/rolebinding.yaml b/helm-chart/binderhub/templates/rolebinding.yaml new file mode 100644 index 000000000..8c2387ba4 --- /dev/null +++ b/helm-chart/binderhub/templates/rolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create -}} +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ include "binderhub.binderhub.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: {{ include "binderhub.binderhub.serviceaccount.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "binderhub.binderhub.fullname" . }} +{{- end }} diff --git a/helm-chart/binderhub/templates/secret.yaml b/helm-chart/binderhub/templates/secret.yaml index e6ee24d5c..3fa984e5c 100644 --- a/helm-chart/binderhub/templates/secret.yaml +++ b/helm-chart/binderhub/templates/secret.yaml @@ -1,42 +1,28 @@ {{- /* - Note that changes to the rendered version of this - file will trigger a restart of the BinderHub pod - through a annotation containing a hash of this - file rendered. + Changes to this rendered manifest triggers a restart of the binderhub + pod as the pod specification includes an annotation with a checksum of this. */ -}} kind: Secret apiVersion: v1 metadata: - name: binder-secret + name: {{ include "binderhub.binderhub.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} type: Opaque stringData: {{- /* - Stash away relevant Helm template values for - the BinderHub Python application to read from - in binderhub_config.py. + To restart the binderhub pod only when relevant, we pick out the + chart configuration actually consumed in the mounted binderhub_config.py + file. */}} - values.yaml: | - {{- pick .Values "config" "imageBuilderType" "cors" "dind" "pink" "extraConfig" | toYaml | nindent 4 }} + chart-config.yaml: | + {{- pick .Values "custom" "config" "extraConfig" | toYaml | nindent 4 }} - {{- /* Glob files to allow them to be mounted by the binderhub pod */}} - {{- /* key=filename: value=content */}} - {{- (.Files.Glob "files/*").AsConfig | nindent 2 }} - - {{- with include "jupyterhub.extraFiles.stringData" .Values.extraFiles }} - {{- . | nindent 2 }} + {{- with .Values.extraCredentials.googleServiceAccountKey }} + gcp-sa-key.json: | + {{- . | nindent 4 }} {{- end }} -{{- with include "jupyterhub.extraFiles.data" .Values.extraFiles }} -data: - {{- . | nindent 2 }} -{{- end }} ---- -{{- if or .Values.config.BinderHub.use_registry .Values.config.BinderHub.buildDockerConfig }} -kind: Secret -apiVersion: v1 -metadata: - name: binder-build-docker-config -type: Opaque -data: - config.json: {{ include "buildDockerConfig" . | b64enc | quote }} -{{- end }} + {{- /* Glob files to allow them to be mounted by the binderhub pod */}} + {{- /* key=filename: value=content */}} + {{- (.Files.Glob "mounted-files/*").AsConfig | nindent 2 }} diff --git a/helm-chart/binderhub/templates/service.yaml b/helm-chart/binderhub/templates/service.yaml index 5fe350568..6eb3a46df 100644 --- a/helm-chart/binderhub/templates/service.yaml +++ b/helm-chart/binderhub/templates/service.yaml @@ -1,24 +1,19 @@ apiVersion: v1 kind: Service metadata: - name: binder - annotations: {{ .Values.service.annotations | toJson }} - labels: {{ .Values.service.labels | toJson }} + name: {{ include "binderhub.binderhub.fullname" . }} + labels: {{- include "binderhub.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} spec: type: {{ .Values.service.type }} - {{- with .Values.service.loadBalancerIP }} - loadBalancerIP: {{ . | quote }} - {{- end }} - selector: - app: binder - name: binder - component: binder - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} ports: - - protocol: TCP - port: 80 - targetPort: 8585 + - name: http + port: {{ .Values.service.port }} + targetPort: http {{- with .Values.service.nodePort }} nodePort: {{ . }} {{- end }} + selector: {{- include "binderhub.selectorLabels" . | nindent 4 }} diff --git a/helm-chart/binderhub/templates/serviceaccount.yaml b/helm-chart/binderhub/templates/serviceaccount.yaml new file mode 100644 index 000000000..6280c3112 --- /dev/null +++ b/helm-chart/binderhub/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "binderhub.binderhub.serviceaccount.fullname" . }} + labels: + {{- include "binderhub.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm-chart/binderhub/values.schema.yaml b/helm-chart/binderhub/values.schema.yaml new file mode 100644 index 000000000..9b8dbf8e7 --- /dev/null +++ b/helm-chart/binderhub/values.schema.yaml @@ -0,0 +1,247 @@ +# This schema (a JSONSchema in YAML format) is used to generate +# values.schema.json to be packaged with the Helm chart. +# +# This schema is also planned to be used by our documentation system to build +# the configuration reference section based on the description fields. See +# docs/source/conf.py for that logic in the future! +# +# We look to document everything we have default values for in values.yaml, but +# we don't look to enforce the perfect validation logic within this file. +# +# ref: https://helm.sh/docs/topics/charts/#schema-files +# ref: https://json-schema.org/learn/getting-started-step-by-step.html +# +$schema: http://json-schema.org/draft-07/schema# +type: object +additionalProperties: false +required: + # General configuration + - global + - custom + # Resources for the BinderHub created build pods + - buildPodsRegistryCredentials + # Deployment resource + - image + - extraCredentials + # Other resources + - rbac + - serviceAccount + - service + - ingress +properties: + # Flag to conditionally install the chart + # --------------------------------------------------------------------------- + # + enabled: + type: boolean + description: | + Configuration flag for charts depending on binderhub to toggle installing it. + + # General configuration + # --------------------------------------------------------------------------- + # + nameOverride: + type: [string, "null"] + fullnameOverride: + type: [string, "null"] + global: + type: object + additionalProperties: true + custom: + type: object + additionalProperties: true + + # Resources for the BinderHub created build pods + # --------------------------------------------------------------------------- + # + buildPodsDockerConfig: + type: object + additionalProperties: true + buildPodsRegistryCredentials: + type: object + additionalProperties: false + required: + - server + - username + - password + properties: + server: + type: string + username: + type: string + password: + type: string + + # Deployment resource + # --------------------------------------------------------------------------- + # + config: + type: object + additionalProperties: false + patternProperties: + ".*": + type: [object, "null"] + additionalProperties: true + extraConfig: + type: object + additionalProperties: false + patternProperties: + ".*": + type: [string, "null"] + extraCredentials: + type: object + additionalProperties: false + properties: + googleServiceAccountKey: + type: string + extraEnv: + type: array + extraVolumes: + type: array + extraVolumeMounts: + type: array + + replicas: + type: integer + image: &image + type: object + additionalProperties: false + required: [repository, tag] + properties: + repository: + type: string + tag: + type: string + pullPolicy: + enum: [null, "", IfNotPresent, Always, Never] + pullSecrets: + type: array + resources: &resources + type: object + additionalProperties: true + securityContext: &securityContext + type: object + additionalProperties: true + podSecurityContext: &podSecurityContext + type: object + additionalProperties: true + podAnnotations: &labels-and-annotations + type: object + additionalProperties: false + patternProperties: + ".*": + type: string + nodeSelector: &nodeSelector + type: object + additionalProperties: true + affinity: &affinity + type: object + additionalProperties: true + tolerations: &tolerations + type: array + + # RBAC resources + # --------------------------------------------------------------------------- + # + rbac: + type: object + additionalProperties: false + required: [create] + properties: + create: + type: boolean + + # ServiceAccount resource + # --------------------------------------------------------------------------- + # + serviceAccount: + type: object + additionalProperties: false + required: [create, name] + properties: + create: + type: boolean + name: + type: string + annotations: *labels-and-annotations + + # Service resource + # --------------------------------------------------------------------------- + # + service: + type: object + additionalProperties: false + required: [type, port] + properties: + type: + type: string + port: + type: integer + nodePort: + type: integer + annotations: *labels-and-annotations + + # Ingress resource + # --------------------------------------------------------------------------- + # + ingress: + type: object + additionalProperties: false + required: [enabled] + properties: + enabled: + type: boolean + annotations: *labels-and-annotations + ingressClassName: + type: [string, "null"] + hosts: + type: array + pathSuffix: + type: [string, "null"] + pathType: + enum: [Prefix, Exact, ImplementationSpecific] + tls: + type: array + + # DaemonSet resource - docker-api + # --------------------------------------------------------------------------- + # + dockerApi: + type: object + additionalProperties: false + required: [image] + properties: + image: *image + resources: *resources + securityContext: *securityContext + podSecurityContext: *podSecurityContext + podAnnotations: *labels-and-annotations + nodeSelector: *nodeSelector + affinity: *affinity + tolerations: *tolerations + extraArgs: + type: array + extraFiles: + type: object + additionalProperties: false + patternProperties: + ".*": + type: object + additionalProperties: false + required: [mountPath] + oneOf: + - required: [data] + - required: [stringData] + - required: [binaryData] + properties: + mountPath: + type: string + data: + type: object + additionalProperties: true + stringData: + type: string + binaryData: + type: string + mode: + type: number diff --git a/helm-chart/binderhub/values.yaml b/helm-chart/binderhub/values.yaml index 3109f0bc8..475055fbe 100644 --- a/helm-chart/binderhub/values.yaml +++ b/helm-chart/binderhub/values.yaml @@ -1,370 +1,137 @@ -pdb: - enabled: true - maxUnavailable: 1 - minAvailable: - +# General configuration +# ----------------------------------------------------------------------------- +# +nameOverride: "" +fullnameOverride: "" +global: {} +custom: {} + +# Resources for the BinderHub created build pods +# ----------------------------------------------------------------------------- +# +buildPodsDockerConfig: {} +buildPodsRegistryCredentials: + server: "" + username: "" + password: "" + +# Deployment resource +# ----------------------------------------------------------------------------- +# + +# The "config" section below is rendered by a k8s Secret and mounted as a .yaml +# file together with binderhub_config.py for the binderhub Python software to +# parse. +# +# Together they make the configured values below translate so that +# config.BinderHub.base_url sets c.BinderHub.base_url, or more generally that +# config.X.y sets c.X.y where X is a class and y is a configurable traitlet on +# the class. +# +# Some config must be set here, and not via extraConfig, as its referenced by +# the chart's template directly. +# +# - BinderHub.base_url (readinessProbe) +# - BinderHub.port (containerPort) +# +config: + BinderHub: + base_url: / + port: 8585 + enable_api_only_mode: true +extraConfig: + # binderhub creates a k8s Secret with a docker config.json file + # including registry credentials. + binderhub-01-build-pods-docker-config: | + import os + c.KubernetesBuildExecutor.push_secret = os.environ["PUSH_SECRET_NAME"] + + binderhub-02-set-docker-api: | + import os + helm_release_name = os.environ["HELM_RELEASE_NAME"] + namespace = os.environ["NAMESPACE"] + c.KubernetesBuildExecutor.docker_host = f"/var/run/{ namespace }-{ helm_release_name }/docker-api/docker-api.sock" + +extraCredentials: + googleServiceAccountKey: "" +extraEnv: [] replicas: 1 - -resources: - requests: - cpu: 0.2 - memory: 512Mi - -rbac: - enabled: true - -nodeSelector: {} -tolerations: [] - image: - name: quay.io/jupyterhub/k8s-binderhub + repository: quay.io/2i2c/binderhub-service tag: "set-by-chartpress" pullPolicy: "" pullSecrets: [] +resources: {} +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +podSecurityContext: {} +podAnnotations: {} +nodeSelector: {} +affinity: {} +tolerations: [] -# registry here is only used to create docker config.json -registry: - # key in 'auths' in docker config.json, - # ~always the registry url - url: - # registry username+password - username: - password: - +# RBAC resources +# ----------------------------------------------------------------------------- +# +rbac: + create: true + +# ServiceAccount resource +# ----------------------------------------------------------------------------- +# +serviceAccount: + create: true + name: "" + annotations: {} + +# Service resource +# ----------------------------------------------------------------------------- +# service: - type: LoadBalancer - labels: {} - annotations: - prometheus.io/scrape: "true" - nodePort: - loadBalancerIP: - -config: - # These c.BinderHub properties are referenced by the Helm chart - BinderHub: - # auth_enabled: - base_url: / - build_node_selector: {} - # hub_url: - # hub_url_local: - use_registry: true - KubernetesBuildExecutor: {} - -extraConfig: {} - -extraFiles: {} - -extraPodSpec: {} - -# Two bits of config need to be set to fully enable cors. -# config.BinderHub.cors_allow_origin controls the allowed origins for the -# binderhub api, and jupyterhub.hub.config.BinderSpawner.cors_allow_origin -# controls the allowed origins for the spawned user notebooks. You most -# likely want to set both of those to the same value. - -jupyterhub: - # Deprecated values, kept here so we can provide useful error messages - custom: - cors: {} - cull: - enabled: true - users: true - hub: - config: - JupyterHub: - authenticator_class: "null" - BinderSpawner: - auth_enabled: false - loadRoles: - binder: - services: - - binder - scopes: - - servers - # admin:users is required in order to create a jupyterhub user for an - # anonymous binderhub web-server visitor in non-authenticated - # deployments, and read:users is required for authenticated - # deployments to check the state of a jupyterhub user's running - # servers before trying to launch. - - admin:users - extraConfig: - 0-binderspawnermixin: | - """ - Helpers for creating BinderSpawners - - FIXME: - This file is defined in binderhub/binderspawner_mixin.py - and is copied to helm-chart/binderhub/values.yaml - by ci/check_embedded_chart_code.py - - The BinderHub repo is just used as the distribution mechanism for this spawner, - BinderHub itself doesn't require this code. - - Longer term options include: - - Move BinderSpawnerMixin to a separate Python package and include it in the Z2JH Hub - image - - Override the Z2JH hub with a custom image built in this repository - - Duplicate the code here and in binderhub/binderspawner_mixin.py - """ - - from tornado import web - from traitlets import Bool, Unicode - from traitlets.config import Configurable - - - class BinderSpawnerMixin(Configurable): - """ - Mixin to convert a JupyterHub container spawner to a BinderHub spawner - - Container spawner must support the following properties that will be set - via spawn options: - - image: Container image to launch - - token: JupyterHub API token - """ - - def __init__(self, *args, **kwargs): - # Is this right? Is it possible to having multiple inheritance with both - # classes using traitlets? - # https://stackoverflow.com/questions/9575409/calling-parent-class-init-with-multiple-inheritance-whats-the-right-way - # https://github.com/ipython/traitlets/pull/175 - super().__init__(*args, **kwargs) - - auth_enabled = Bool( - False, - help=""" - Enable authenticated binderhub setup. - - Requires `jupyterhub-singleuser` to be available inside the repositories - being built. - """, - config=True, - ) - - cors_allow_origin = Unicode( - "", - help=""" - Origins that can access the spawned notebooks. - - Sets the Access-Control-Allow-Origin header in the spawned - notebooks. Set to '*' to allow any origin to access spawned - notebook servers. - - See also BinderHub.cors_allow_origin in binderhub config - for controlling CORS policy for the BinderHub API endpoint. - """, - config=True, - ) - - def get_args(self): - if self.auth_enabled: - args = super().get_args() - else: - args = [ - "--ip=0.0.0.0", - f"--port={self.port}", - f"--NotebookApp.base_url={self.server.base_url}", - f"--NotebookApp.token={self.user_options['token']}", - "--NotebookApp.trust_xheaders=True", - ] - if self.default_url: - args.append(f"--NotebookApp.default_url={self.default_url}") - - if self.cors_allow_origin: - args.append("--NotebookApp.allow_origin=" + self.cors_allow_origin) - # allow_origin=* doesn't properly allow cross-origin requests to single files - # see https://github.com/jupyter/notebook/pull/5898 - if self.cors_allow_origin == "*": - args.append("--NotebookApp.allow_origin_pat=.*") - args += self.args - # ServerApp compatibility: duplicate NotebookApp args - for arg in list(args): - if arg.startswith("--NotebookApp."): - args.append(arg.replace("--NotebookApp.", "--ServerApp.")) - return args - - def start(self): - if not self.auth_enabled: - if "token" not in self.user_options: - raise web.HTTPError(400, "token required") - if "image" not in self.user_options: - raise web.HTTPError(400, "image required") - if "image" in self.user_options: - self.image = self.user_options["image"] - return super().start() - - def get_env(self): - env = super().get_env() - if "repo_url" in self.user_options: - env["BINDER_REPO_URL"] = self.user_options["repo_url"] - for key in ( - "binder_ref_url", - "binder_launch_host", - "binder_persistent_request", - "binder_request", - ): - if key in self.user_options: - env[key.upper()] = self.user_options[key] - return env - - 00-binder: | - # image & token are set via spawn options - from kubespawner import KubeSpawner - - class BinderSpawner(BinderSpawnerMixin, KubeSpawner): - pass - - c.JupyterHub.spawner_class = BinderSpawner - services: - binder: - display: false - singleuser: - # start jupyterlab server *if available* - # fallback on jupyter-notebook - cmd: - - python3 - - "-c" - - | - import os - import sys - - try: - import jupyterlab - import jupyterlab.labapp - major = int(jupyterlab.__version__.split(".", 1)[0]) - except Exception as e: - print("Failed to import jupyterlab: {e}", file=sys.stderr) - have_lab = False - else: - have_lab = major >= 3 - - if have_lab: - # technically, we could accept another jupyter-server-based frontend - print("Launching jupyter-lab", file=sys.stderr) - exe = "jupyter-lab" - else: - print("jupyter-lab not found, launching jupyter-notebook", file=sys.stderr) - exe = "jupyter-notebook" - - # launch the notebook server - os.execvp(exe, sys.argv) - events: true - storage: - type: none - memory: - guarantee: - prePuller: - hook: - enabled: false - continuous: - enabled: false - -deployment: - readinessProbe: - enabled: true - initialDelaySeconds: 0 - periodSeconds: 5 - failureThreshold: 1000 # we rely on the liveness probe to resolve issues if needed - timeoutSeconds: 3 - livenessProbe: - enabled: true - initialDelaySeconds: 10 - periodSeconds: 5 - failureThreshold: 3 - timeoutSeconds: 10 - labels: {} - -imageBuilderType: "host" - -dind: - initContainers: [] - daemonset: - image: - name: docker.io/library/docker - tag: "27.5.1-dind" # ref: https://hub.docker.com/_/docker/tags - pullPolicy: "" - pullSecrets: [] - # Additional command line arguments to pass to dockerd - extraArgs: [] - lifecycle: {} - extraVolumes: [] - extraVolumeMounts: [] - storageDriver: overlay2 - resources: {} - hostSocketDir: /var/run/dind - hostLibDir: /var/lib/dind - hostSocketName: docker.sock - -# Podman in Kubernetes -pink: - initContainers: [] - daemonset: - image: - name: quay.io/podman/stable - tag: "v5.3.2" # ref: https://quay.io/repository/podman/stable - pullPolicy: "" - pullSecrets: [] - lifecycle: {} - extraVolumes: [] - extraVolumeMounts: [] - resources: {} - hostStorageDir: /var/lib/pink/storage - hostSocketDir: /var/run/pink - hostSocketName: podman.sock - -imageCleaner: - enabled: true - extraEnv: {} - image: - name: quay.io/jupyterhub/docker-image-cleaner - tag: "1.0.0-beta.3" - pullPolicy: "" - pullSecrets: [] - # whether to cordon nodes while cleaning - cordon: true - # delete an image at most every 5 seconds - delay: 5 - # Interpret threshold values as percentage or bytes - imageGCThresholdType: "relative" - # when 80% of inodes are used, - # cull images until it drops below 60% - imageGCThresholdHigh: 80 - imageGCThresholdLow: 60 - # cull images on the host docker as well as dind - # configuration to use if `imageBuilderType: host` is configured - host: - dockerSocket: /var/run/docker.sock - dockerLibDir: /var/lib/docker + type: ClusterIP + port: 80 +# Ingress resource +# ----------------------------------------------------------------------------- +# ingress: enabled: false - https: - enabled: false - type: kube-lego - hosts: [] + annotations: {} ingressClassName: - annotations: - {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" + hosts: [] pathSuffix: - # Suffix added to Ingress's routing path pattern. - # Specify `*` if your ingress matches path by glob pattern. pathType: Prefix - tls: - [] - # Secrets must be manually created in the namespace. - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -initContainers: [] -lifecycle: {} -extraVolumes: [] -extraVolumeMounts: [] -extraEnv: {} -podAnnotations: {} - -# Deprecated values, kept here so we can provide useful error messages -cors: {} - -global: {} + tls: [] + +# DaemonSet resource - docker-api +# ----------------------------------------------------------------------------- +# +# This DaemonSet starts a pod on each node to setup a Docker API that +# binderhub's spawned build pods can make use of, via a hostPath volume that +# exposes a unix socket. +# +dockerApi: + image: + repository: docker.io/library/docker + tag: "27.5.1-dind" # source: https://hub.docker.com/_/docker/tags + pullPolicy: "" + pullSecrets: [] + resources: {} + securityContext: + privileged: true + runAsUser: 0 + + podSecurityContext: {} + podAnnotations: {} + nodeSelector: {} + affinity: {} + tolerations: [] + + extraArgs: [] + extraFiles: {} diff --git a/helm-chart/images/binderhub/Dockerfile b/helm-chart/images/binderhub/Dockerfile index 7393827ef..68480b562 100644 --- a/helm-chart/images/binderhub/Dockerfile +++ b/helm-chart/images/binderhub/Dockerfile @@ -10,42 +10,41 @@ # FROM python:3.13-bookworm as build-stage +RUN apt-get update \ + && apt-get install --yes \ + nodejs npm \ + && rm -rf /var/lib/apt/lists/* + # Build wheels # # We set pip's cache directory and expose it across build stages via an # ephemeral docker cache (--mount=type=cache,target=${PIP_CACHE_DIR}). We use # the same technique for the directory /tmp/wheels. # -# assumes `python3 -m build .` has been run to create the wheel -# in the top-level dist directory -COPY helm-chart/images/binderhub/requirements.txt ./ -COPY dist . +COPY . /src ARG PIP_CACHE_DIR=/tmp/pip-cache RUN --mount=type=cache,target=${PIP_CACHE_DIR} \ - pip wheel --wheel-dir=/tmp/wheels \ + pip wheel \ + --wheel-dir=/tmp/wheels \ # pycurl wheels for 7.45.3 have problems finding CAs # https://github.com/pycurl/pycurl/issues/834 --no-binary pycurl \ - -r ./requirements.txt \ - ./binderhub*.whl - + -r /src/helm-chart/images/binderhub/requirements.txt # The final stage # --------------- -# This stage is built and published as quay.io/jupyterhub/k8s-binderhub. +# This stage is built and published as quay.io/2i2c/binderhub. # FROM python:3.13-slim-bookworm -ENV PYTHONUNBUFFERED=1 -ENV DEBIAN_FRONTEND=noninteractive - +ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get upgrade --yes \ && apt-get install --yes \ git \ # required by binderhub libcurl4 \ - # required by pycurl + # required by pycurl, an optional binderhub dependency tini \ # tini is used as an entrypoint to not loose track of SIGTERM # signals as sent before SIGKILL, for example when "docker stop" @@ -53,17 +52,24 @@ RUN apt-get update \ # terminate very quickly. && rm -rf /var/lib/apt/lists/* +COPY helm-chart/images/binderhub/requirements.txt requirements.txt +# Even though a binderhub wheel is built by the build stage, the requirements.txt +# file produced by pip-compile will still refer to `file:///src` as the source for it. +# We run this sed to tell it to just look for a package named `binderhub` instead, which +# will show up as a wheel in `/tmp/wheels` +RUN sed -i -E 's/binderhub @ file.+/binderhub/' requirements.txt + # install wheels built in the build stage -COPY helm-chart/images/binderhub/requirements.txt /tmp/requirements.txt ARG PIP_CACHE_DIR=/tmp/pip-cache +# --no-index ensures _only_ wheels from the build stage are installed RUN --mount=type=cache,target=${PIP_CACHE_DIR} \ --mount=type=cache,from=build-stage,source=/tmp/wheels,target=/tmp/wheels \ - pip install --no-deps /tmp/wheels/* \ - # validate pip install since it's not resolving dependencies - && pip check \ - # verify success of previous step - && python -c "import pycurl, binderhub.app" + pip install \ + --no-index \ + --find-links=/tmp/wheels/ \ + -r requirements.txt +ENV PYTHONUNBUFFERED=1 EXPOSE 8585 -ENTRYPOINT ["tini", "--", "python", "-m", "binderhub"] -CMD ["--config", "/etc/binderhub/config/binderhub_config.py"] +ENTRYPOINT ["tini", "--"] +CMD ["python", "-m", "binderhub", "--config", "/etc/binderhub/mounted-secret/binderhub_config.py"] diff --git a/helm-chart/images/binderhub/README.md b/helm-chart/images/binderhub/README.md index b34f85924..7a1471254 100644 --- a/helm-chart/images/binderhub/README.md +++ b/helm-chart/images/binderhub/README.md @@ -1,11 +1,11 @@ -# binderhub image +# About this folder -The image for running binderhub itself. -Built with [chartpress][]. +The Dockerfile in this folder is built by +[chartpress](https://github.com/jupyterhub/chartpress#readme), using the +requirements.txt file. The requirements.txt file is updated based on the +requirements.in file using [`pip-compile`](https://pip-tools.readthedocs.io). -[chartpress]: https://github.com/jupyterhub/chartpress - -## Updating requirements.txt +## How to update requirements.txt Use the "Run workflow" button at https://github.com/jupyterhub/binderhub/actions/workflows/watch-dependencies.yaml. diff --git a/helm-chart/images/binderhub/requirements.in b/helm-chart/images/binderhub/requirements.in index 1b462744b..6cd668d53 100644 --- a/helm-chart/images/binderhub/requirements.in +++ b/helm-chart/images/binderhub/requirements.in @@ -1,20 +1,3 @@ -# google-cloud-logging is an optional dependency used by mybinder.org-deploy at -# https://github.com/jupyterhub/mybinder.org-deploy/blob/e47021fe/mybinder/values.yaml#L193-L216. -# -# We pin it to avoid introducing a potentially breaking change as major versions -# are released. See: -# https://github.com/googleapis/python-logging/blob/master/UPGRADING.md -# -google-cloud-logging==3.* - -# jupyterhub's major version should be matched with the JupyterHub Helm chart's -# used version of JupyterHub. -# -jupyterhub==4.* - -# https://github.com/kubernetes-client/python -kubernetes==9.* - # binderhub's dependencies # # We can't put ".[pycurl]" here directly as when we freeze this into @@ -22,4 +5,15 @@ kubernetes==9.* # which is a problem as its an absolute path. # pycurl --r ../../../requirements.txt +# This *must* be regenerated with `./ci/refreeze` so it handles paths correctly +. + + +# ruamel-yaml is required by this chart's binderhub_config.py +ruamel-yaml + +# google-cloud-logging is an optional dependency to help log BinderHub launch +# events to a Google Cloud Logging as done by mybinder.org deployments here: +# https://github.com/jupyterhub/mybinder.org-deploy/blob/e47021fe/mybinder/values.yaml#L193-L216. +# +google-cloud-logging==3.* diff --git a/helm-chart/images/binderhub/requirements.txt b/helm-chart/images/binderhub/requirements.txt index d9e6749af..603ae37f8 100644 --- a/helm-chart/images/binderhub/requirements.txt +++ b/helm-chart/images/binderhub/requirements.txt @@ -4,15 +4,19 @@ # # Use the "Run workflow" button at https://github.com/jupyterhub/binderhub/actions/workflows/watch-dependencies.yaml # -alembic==1.14.1 - # via jupyterhub -async-generator==1.10 +alembic==1.15.1 # via jupyterhub +annotated-types==0.7.0 + # via pydantic +arrow==1.3.0 + # via isoduration attrs==25.1.0 # via # jsonschema # referencing -cachetools==5.5.1 +binderhub @ file:///src + # via -r helm-chart/images/binderhub/requirements.in +cachetools==5.5.2 # via google-auth certifi==2025.1.31 # via @@ -24,14 +28,18 @@ cffi==1.17.1 # via cryptography charset-normalizer==3.4.1 # via requests -cryptography==44.0.1 +cryptography==44.0.2 # via certipy deprecated==1.2.18 # via opentelemetry-api docker==7.1.0 - # via -r /io/requirements.txt + # via binderhub +durationpy==0.9 + # via kubernetes escapism==1.0.1 - # via -r /io/requirements.txt + # via binderhub +fqdn==1.5.1 + # via jsonschema google-api-core==2.24.1 # via # google-cloud-appengine-logging @@ -46,13 +54,13 @@ google-auth==2.38.0 # kubernetes google-cloud-appengine-logging==1.6.0 # via google-cloud-logging -google-cloud-audit-log==0.3.0 +google-cloud-audit-log==0.3.1 # via google-cloud-logging -google-cloud-core==2.4.1 +google-cloud-core==2.4.2 # via google-cloud-logging google-cloud-logging==3.11.4 # via -r helm-chart/images/binderhub/requirements.in -googleapis-common-protos==1.67.0 +googleapis-common-protos==1.69.1 # via # google-api-core # google-cloud-audit-log @@ -60,7 +68,7 @@ googleapis-common-protos==1.67.0 # grpcio-status greenlet==3.1.1 # via sqlalchemy -grpc-google-iam-v1==0.14.0 +grpc-google-iam-v1==0.14.1 # via google-cloud-logging grpcio==1.70.0 # via @@ -71,29 +79,32 @@ grpcio==1.70.0 grpcio-status==1.70.0 # via google-api-core idna==3.10 - # via requests + # via + # jsonschema + # jupyterhub + # requests importlib-metadata==8.5.0 # via opentelemetry-api -jinja2==3.1.5 +isoduration==20.11.0 + # via jsonschema +jinja2==3.1.6 # via - # -r /io/requirements.txt + # binderhub # jupyterhub +jsonpointer==3.0.0 + # via jsonschema jsonschema==4.23.0 # via - # -r /io/requirements.txt - # jupyter-telemetry + # binderhub + # jupyter-events jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-telemetry==0.1.0 +jupyter-events==0.12.0 # via jupyterhub -jupyterhub==4.1.6 - # via - # -r /io/requirements.txt - # -r helm-chart/images/binderhub/requirements.in -kubernetes==9.0.1 - # via - # -r /io/requirements.txt - # -r helm-chart/images/binderhub/requirements.in +jupyterhub==5.2.1 + # via binderhub +kubernetes==32.0.1 + # via binderhub mako==1.3.9 # via alembic markupsafe==3.0.2 @@ -103,16 +114,19 @@ markupsafe==3.0.2 oauthlib==3.2.2 # via # jupyterhub + # kubernetes # requests-oauthlib opentelemetry-api==1.30.0 # via google-cloud-logging packaging==24.2 - # via jupyterhub + # via + # jupyter-events + # jupyterhub pamela==1.2.0 # via jupyterhub prometheus-client==0.21.1 # via - # -r /io/requirements.txt + # binderhub # jupyterhub proto-plus==1.26.0 # via @@ -137,24 +151,32 @@ pyasn1-modules==0.4.1 # via google-auth pycparser==2.22 # via cffi -pycurl==7.45.4 +pycurl==7.45.6 # via -r helm-chart/images/binderhub/requirements.in +pydantic==2.10.6 + # via jupyterhub +pydantic-core==2.27.2 + # via pydantic pyjwt==2.10.1 - # via -r /io/requirements.txt + # via binderhub python-dateutil==2.9.0.post0 # via + # arrow # jupyterhub # kubernetes -python-json-logger==3.2.1 +python-json-logger==3.3.0 # via - # -r /io/requirements.txt - # jupyter-telemetry + # binderhub + # jupyter-events pyyaml==6.0.2 - # via kubernetes + # via + # jupyter-events + # kubernetes referencing==0.36.2 # via # jsonschema # jsonschema-specifications + # jupyter-events requests==2.32.3 # via # docker @@ -164,47 +186,60 @@ requests==2.32.3 # requests-oauthlib requests-oauthlib==2.0.0 # via kubernetes -rpds-py==0.22.3 +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rpds-py==0.23.1 # via # jsonschema # referencing rsa==4.9 # via google-auth ruamel-yaml==0.18.10 - # via jupyter-telemetry + # via -r helm-chart/images/binderhub/requirements.in six==1.17.0 # via # kubernetes # python-dateutil + # rfc3339-validator sqlalchemy==2.0.38 # via # alembic # jupyterhub tornado==6.4.2 # via - # -r /io/requirements.txt + # binderhub # jupyterhub traitlets==5.14.3 # via - # -r /io/requirements.txt - # jupyter-telemetry + # binderhub + # jupyter-events # jupyterhub +types-python-dateutil==2.9.0.20241206 + # via arrow typing-extensions==4.12.2 # via # alembic + # pydantic + # pydantic-core # sqlalchemy +uri-template==1.3.0 + # via jsonschema urllib3==2.3.0 # via # docker # kubernetes # requests +webcolors==24.11.1 + # via jsonschema websocket-client==1.8.0 # via kubernetes wrapt==1.17.2 # via deprecated zipp==3.21.0 # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.8.0 - # via kubernetes