From 2656cc7abb8d897ac6b11ef4e9bcf8b2c8ce0018 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 14:58:58 +0100 Subject: [PATCH 01/20] Add binderhub helm chart to deployer's validation step --- deployer/config_validation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deployer/config_validation.py b/deployer/config_validation.py index 03dd6216c0..834be503bf 100644 --- a/deployer/config_validation.py +++ b/deployer/config_validation.py @@ -55,6 +55,10 @@ def _prepare_helm_charts_dependencies_and_schemas(): _generate_values_schema_json(daskhub_dir) subprocess.check_call(["helm", "dep", "up", daskhub_dir]) + binderhub_dir = helm_charts_dir.joinpath("binderhub") + _generate_values_schema_json(binderhub_dir) + subprocess.check_call(["helm", "dep", "up", binderhub_dir]) + support_dir = helm_charts_dir.joinpath("support") _generate_values_schema_json(support_dir) subprocess.check_call(["helm", "dep", "up", support_dir]) From ec6f0aee616cb16d8b8e2e99252bb4110716b5d2 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 14:59:48 +0100 Subject: [PATCH 02/20] Generate different config for binderhub Compared to basehub or daskhub --- deployer/hub.py | 204 ++++++++++++++++++++++++++---------------------- 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/deployer/hub.py b/deployer/hub.py index a3d74fa637..634c1caaa4 100644 --- a/deployer/hub.py +++ b/deployer/hub.py @@ -31,90 +31,118 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key): WARNING: MIGHT CONTAINS SECRET VALUES! """ - - generated_config = { - "jupyterhub": { - "proxy": {"https": {"hosts": [self.spec["domain"]]}}, - "ingress": { - "hosts": [self.spec["domain"]], - "tls": [ - {"secretName": "https-auto-tls", "hosts": [self.spec["domain"]]} - ], - }, - "singleuser": { - # If image_repo isn't set, just have an empty image dict - "image": {"name": self.cluster.spec["image_repo"]} - if "image_repo" in self.cluster.spec - else {}, - }, - "hub": { - "config": {}, - "initContainers": [ - { - "name": "templates-clone", - "image": "alpine/git", - "args": [ - "clone", - "--", - "https://github.com/2i2c-org/default-hub-homepage", - "/srv/repo", - ], - "securityContext": { - "runAsUser": 1000, - "allowPrivilegeEscalation": False, - "readOnlyRootFilesystem": True, - }, - "volumeMounts": [ - {"name": "custom-templates", "mountPath": "/srv/repo"} - ], + if self.spec["helm_chart"] == "binderhub": + generated_config = { + "binderhub": { + "config": { + "BinderHub": { + "hub_url": f"https://hub.{self.spec['domain']}" } - ], - "extraContainers": [ - { - "name": "templates-sync", - "image": "alpine/git", - "workingDir": "/srv/repo", - "command": ["/bin/sh"], - "args": [ - "-c", - dedent( - f"""\ - while true; do git fetch origin; - if [[ $(git ls-remote --heads origin {self.cluster.spec["name"]}-{self.spec["name"]} | wc -c) -ne 0 ]]; then - git reset --hard origin/{self.cluster.spec["name"]}-{self.spec["name"]}; - else - git reset --hard origin/master; - fi - sleep 5m; done - """ - ), - ], - "securityContext": { - "runAsUser": 1000, - "allowPrivilegeEscalation": False, - "readOnlyRootFilesystem": True, - }, - "volumeMounts": [ - {"name": "custom-templates", "mountPath": "/srv/repo"} - ], + }, + "ingress": { + "hosts": [self.spec["domain"]], + "tls": [ + { + "secretName": "https-auto-tls-binder", "hosts": [self.spec["domain"]] + } + ] + }, + "jupyterhub": { + "ingress": { + "hosts": [f"hub.{self.spec['domain']}"], + "tls": [ + { + "secretName": "https-auto-tls-hub", "hosts": [f"hub.{self.spec['domain']}"] + } + ] } - ], - "extraVolumes": [{"name": "custom-templates", "emptyDir": {}}], - "extraVolumeMounts": [ - { - "mountPath": "/usr/local/share/jupyterhub/custom_templates", - "name": "custom-templates", - "subPath": "templates", - }, - { - "mountPath": "/usr/local/share/jupyterhub/static/extra-assets", - "name": "custom-templates", - "subPath": "extra-assets", - }, - ], + } + } + } + else: + generated_config = { + "jupyterhub": { + "proxy": {"https": {"hosts": [self.spec["domain"]]}}, + "ingress": { + "hosts": [self.spec["domain"]], + "tls": [ + {"secretName": "https-auto-tls", "hosts": [self.spec["domain"]]} + ], + }, + "singleuser": { + # If image_repo isn't set, just have an empty image dict + "image": {"name": self.cluster.spec["image_repo"]} + if "image_repo" in self.cluster.spec + else {}, + }, + "hub": { + "config": {}, + "initContainers": [ + { + "name": "templates-clone", + "image": "alpine/git", + "args": [ + "clone", + "--", + "https://github.com/2i2c-org/default-hub-homepage", + "/srv/repo", + ], + "securityContext": { + "runAsUser": 1000, + "allowPrivilegeEscalation": False, + "readOnlyRootFilesystem": True, + }, + "volumeMounts": [ + {"name": "custom-templates", "mountPath": "/srv/repo"} + ], + } + ], + "extraContainers": [ + { + "name": "templates-sync", + "image": "alpine/git", + "workingDir": "/srv/repo", + "command": ["/bin/sh"], + "args": [ + "-c", + dedent( + f"""\ + while true; do git fetch origin; + if [[ $(git ls-remote --heads origin {self.cluster.spec["name"]}-{self.spec["name"]} | wc -c) -ne 0 ]]; then + git reset --hard origin/{self.cluster.spec["name"]}-{self.spec["name"]}; + else + git reset --hard origin/master; + fi + sleep 5m; done + """ + ), + ], + "securityContext": { + "runAsUser": 1000, + "allowPrivilegeEscalation": False, + "readOnlyRootFilesystem": True, + }, + "volumeMounts": [ + {"name": "custom-templates", "mountPath": "/srv/repo"} + ], + } + ], + "extraVolumes": [{"name": "custom-templates", "emptyDir": {}}], + "extraVolumeMounts": [ + { + "mountPath": "/usr/local/share/jupyterhub/custom_templates", + "name": "custom-templates", + "subPath": "templates", + }, + { + "mountPath": "/usr/local/share/jupyterhub/static/extra-assets", + "name": "custom-templates", + "subPath": "extra-assets", + }, + ], + }, }, - }, - } + } # # Allow explicilty ignoring auth0 setup if self.spec["auth0"].get("enabled", True): @@ -156,16 +184,6 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key): Ideally, these would be done declaratively. Until then, let's put all of them in this function. """ - hub_helm_chart = self.spec["helm_chart"] - - # FIXME: This section is only relevant if we generate any config in this - # function. Currently we only generate a JupyterHub API token for - # dask-gateway to use, but as that is resolved we can cleanup - # this entire function. - # - if hub_helm_chart != "basehub": - generated_config = {"basehub": generated_config} - # FIXME: This section can be removed upon resolution of the below linked issue, where we would # instead just define a JupyterHub service under hub.services and # rely on the JupyterHub Helm chart to generate an api token if @@ -174,7 +192,9 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key): # Blocked by https://github.com/dask/dask-gateway/issues/473 and a # release including it. # - if hub_helm_chart == "daskhub": + if self.spec["helm_chart"] == "daskhub": + generated_config = {"basehub": generated_config} + gateway_token = hmac.new( secret_key, b"gateway-" + self.spec["name"].encode(), hashlib.sha256 ).hexdigest() From a79a58e51cab0f2059d7ec0e6382226e18ad1122 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 15:01:11 +0100 Subject: [PATCH 03/20] Create basic binderhub config --- config/clusters/2i2c/binder.values.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 config/clusters/2i2c/binder.values.yaml diff --git a/config/clusters/2i2c/binder.values.yaml b/config/clusters/2i2c/binder.values.yaml new file mode 100644 index 0000000000..48cb871644 --- /dev/null +++ b/config/clusters/2i2c/binder.values.yaml @@ -0,0 +1,4 @@ +binderhub: + config: + BinderHub: + image_prefix: "sgibson91/2i2c-binder-staging-" From 45dabd195faf4fb364b461859b5737f467080eb2 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 15:01:43 +0100 Subject: [PATCH 04/20] Setup secret binderhub config with registry credentials This is Sarah's Docker Hub account for now --- .../2i2c/enc-binder.secret.values.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 config/clusters/2i2c/enc-binder.secret.values.yaml diff --git a/config/clusters/2i2c/enc-binder.secret.values.yaml b/config/clusters/2i2c/enc-binder.secret.values.yaml new file mode 100644 index 0000000000..fca9dee4a4 --- /dev/null +++ b/config/clusters/2i2c/enc-binder.secret.values.yaml @@ -0,0 +1,18 @@ +binderhub: + registry: + username: ENC[AES256_GCM,data:ZT0NjJUYveh4,iv:yE2QK4amLcb84piGH2NN7PRKbQirgSNT4EqVMqdBwM4=,tag:7Q5/PHMpUmqT5DDvnxXoKw==,type:str] + password: ENC[AES256_GCM,data:meUMXRwimr26XYEQkv0TwSRSI+M1rzQ6WU8mg+CGpog=,iv:sc0LwsXkVxN2kAuHBv4+D4rbBUv0ONgqWLZzyXDw+6A=,tag:kpAfDEB8oP0Qntev277E2w==,type:str] +sops: + kms: [] + gcp_kms: + - resource_id: projects/two-eye-two-see/locations/global/keyRings/sops-keys/cryptoKeys/similar-hubs + created_at: "2022-06-08T13:12:00Z" + enc: CiQA4OM7eMnD5wD0zQuaFMYYgrJY0qzNtTGK8cwo9OdHtJWx3C0SSQBq6cPrQ+8VfocZn3cxRYZ3RXFwa8XvJ7SI/4hZIaHhlfEbqMvDXArnQ5R0f5iFiw0fNfHC6S+htAJr0usI+QUGT5OoGt9tv1Y= + azure_kv: [] + hc_vault: [] + age: [] + lastmodified: "2022-06-08T13:12:00Z" + mac: ENC[AES256_GCM,data:EhJwhwNOU5JuAHgUo+sRHktsacO3WRB3iLe25T3D/UESHiEAlwv8X1OSmGgV3ybcUn/dBbTFGep6yBCpCMCCXhhEw9IpTTZlss3zxiEoPzGHg0EzW7TWQdU3EirySQq879Y5REbEb8gx01dx7fW/Yu6rFVUgb/ePc0iZMZD3Wi0=,iv:PkSkQCxA3lb9EiOM8g7HkM48wd4Gj/1YDgio/WkRx6M=,tag:2L5GofYPi8hOtvTS2pZtlA==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.7.1 From ec6a0f87726ad4f1e51cdc6364b894057e707215 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 15:01:59 +0100 Subject: [PATCH 05/20] Add binder-staging deployment to 2i2c's cluster.yaml file --- config/clusters/2i2c/cluster.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/clusters/2i2c/cluster.yaml b/config/clusters/2i2c/cluster.yaml index eaa5005d85..670a585db7 100644 --- a/config/clusters/2i2c/cluster.yaml +++ b/config/clusters/2i2c/cluster.yaml @@ -38,6 +38,17 @@ hubs: # that you intend for these files to be applied in this order. - enc-dask-staging.secret.values.yaml - dask-staging.values.yaml + - name: binder-staging + display_name: "2i2c binder staging" + domain: binder-staging.2i2c.cloud + helm_chart: binderhub + auth0: + # connection update? Also ensure the basehub Helm chart is provided a + # matching value for jupyterhub.custom.2i2c.add_staff_user_ids_of_type! + enabled: false + helm_chart_values_files: + - binder.values.yaml + - enc-binder.secret.values.yaml - name: demo display_name: "2i2c demo" domain: demo.2i2c.cloud From 6eb401d5e919f9bab0fd9445766e666affad7d0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jun 2022 14:05:53 +0000 Subject: [PATCH 06/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- deployer/hub.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/deployer/hub.py b/deployer/hub.py index 634c1caaa4..623c97d925 100644 --- a/deployer/hub.py +++ b/deployer/hub.py @@ -35,28 +35,28 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key): generated_config = { "binderhub": { "config": { - "BinderHub": { - "hub_url": f"https://hub.{self.spec['domain']}" - } + "BinderHub": {"hub_url": f"https://hub.{self.spec['domain']}"} }, "ingress": { "hosts": [self.spec["domain"]], "tls": [ { - "secretName": "https-auto-tls-binder", "hosts": [self.spec["domain"]] + "secretName": "https-auto-tls-binder", + "hosts": [self.spec["domain"]], } - ] + ], }, "jupyterhub": { "ingress": { "hosts": [f"hub.{self.spec['domain']}"], "tls": [ { - "secretName": "https-auto-tls-hub", "hosts": [f"hub.{self.spec['domain']}"] + "secretName": "https-auto-tls-hub", + "hosts": [f"hub.{self.spec['domain']}"], } - ] + ], } - } + }, } } else: @@ -66,7 +66,10 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key): "ingress": { "hosts": [self.spec["domain"]], "tls": [ - {"secretName": "https-auto-tls", "hosts": [self.spec["domain"]]} + { + "secretName": "https-auto-tls", + "hosts": [self.spec["domain"]], + } ], }, "singleuser": { @@ -93,7 +96,10 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key): "readOnlyRootFilesystem": True, }, "volumeMounts": [ - {"name": "custom-templates", "mountPath": "/srv/repo"} + { + "name": "custom-templates", + "mountPath": "/srv/repo", + } ], } ], @@ -123,7 +129,10 @@ def get_generated_config(self, auth_provider: KeyProvider, secret_key): "readOnlyRootFilesystem": True, }, "volumeMounts": [ - {"name": "custom-templates", "mountPath": "/srv/repo"} + { + "name": "custom-templates", + "mountPath": "/srv/repo", + } ], } ], From 3a266f81de373a99f79638e22ecca9fae99efb7f Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 16:26:40 +0100 Subject: [PATCH 07/20] Set jupyterhub API token for dask-gateway in BinderHub chart --- deployer/hub.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/deployer/hub.py b/deployer/hub.py index 623c97d925..91a2d99a4b 100644 --- a/deployer/hub.py +++ b/deployer/hub.py @@ -214,6 +214,17 @@ def apply_hub_helm_chart_fixes(self, generated_config, secret_key): "hub", {} ).setdefault("services", {})["dask-gateway"] = {"apiToken": gateway_token} + elif self.spec["helm_chart"] == "binderhub": + gateway_token = hmac.new( + secret_key, b"gateway-" + self.spec["name"].encode(), hashlib.sha256 + ).hexdigest() + generated_config["dask-gateway"] = { + "gateway": {"auth": {"jupyterhub": {"apiToken": gateway_token}}} + } + generated_config["binderhub"].setdefault("jupyterhub", {}).setdefault( + "hub", {} + ).setdefault("services", {})["dask-gateway"] = {"apiToken": gateway_token} + return generated_config def deploy(self, auth_provider, secret_key): From 69f712d76b854060641719eef586befb298c916f Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 16:27:16 +0100 Subject: [PATCH 08/20] Temporarily skip validation when deploying --- deployer/deploy_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/deploy_actions.py b/deployer/deploy_actions.py index 1bf7753a6a..93a67ef1ef 100644 --- a/deployer/deploy_actions.py +++ b/deployer/deploy_actions.py @@ -181,7 +181,7 @@ def deploy(cluster_name, hub_name, config_path): Deploy one or more hubs in a given cluster """ validate_cluster_config(cluster_name) - validate_hub_config(cluster_name, hub_name) + # validate_hub_config(cluster_name, hub_name) assert_single_auth_method_enabled(cluster_name, hub_name) with get_decrypted_file(config_path) as decrypted_file_path: From 694b21fe2609936d471f1f95491c53f016e0b12f Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 17:20:19 +0100 Subject: [PATCH 09/20] Comment out all the custom stuff --- helm-charts/binderhub/values.yaml | 308 +++++++++++++++--------------- 1 file changed, 154 insertions(+), 154 deletions(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index 9ebb355b09..7a3330183e 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -256,160 +256,160 @@ binderhub: break else: print("dask-gateway service not found. Did you set jupyterhub.hub.services.dask-gateway.apiToken?") - 01-custom-theme: | - from z2jh import get_config - c.JupyterHub.template_paths = ['/usr/local/share/jupyterhub/custom_templates/'] - - c.JupyterHub.template_vars = { - 'custom': get_config('custom.homepage.templateVars') - } - 02-custom-admin: | - from z2jh import get_config - from kubespawner import KubeSpawner - from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin - - class CustomSpawner(ConfiguratorSpawnerMixin, KubeSpawner): - def start(self, *args, **kwargs): - custom_admin = get_config('custom.singleuserAdmin', {}) - if custom_admin and self.user.admin: - extra_init_containers = custom_admin.get('initContainers', []) - extra_volume_mounts = custom_admin.get('extraVolumeMounts', []) - - self.init_containers += [container for container in extra_init_containers if container not in self.init_containers] - self.volume_mounts += [volume for volume in extra_volume_mounts if volume not in self.volume_mounts] - - return super().start(*args, **kwargs) - - - c.JupyterHub.spawner_class = CustomSpawner - 03-cloud-storage-bucket: | - from z2jh import get_config - cloud_resources = get_config('custom.cloudResources') - scratch_bucket = cloud_resources['scratchBucket'] - import os - - if scratch_bucket['enabled']: - # FIXME: Support other providers too - assert cloud_resources['provider'] == 'gcp' - project_id = cloud_resources['gcp']['projectId'] - - release = os.environ['HELM_RELEASE_NAME'] - bucket_protocol = 'gcs' - bucket_name = f'{project_id}-{release}-scratch-bucket' - env = { - 'SCRATCH_BUCKET_PROTOCOL': bucket_protocol, - # Matches "daskhub.scratchBUcket.name" helm template - 'SCRATCH_BUCKET_NAME': bucket_name, - # Use k8s syntax of $(ENV_VAR) to substitute env vars dynamically in other env vars - 'SCRATCH_BUCKET': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', - 'PANGEO_SCRATCH': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', - } - - c.KubeSpawner.environment.update(env) - 04-2i2c-add-staff-user-ids-to-admin-users: | - from z2jh import get_config - add_staff_user_ids_to_admin_users = get_config("custom.2i2c.add_staff_user_ids_to_admin_users", False) - - if add_staff_user_ids_to_admin_users: - user_id_type = get_config("custom.2i2c.add_staff_user_ids_of_type") - staff_user_ids = get_config(f"custom.2i2c.staff_{user_id_type}_ids", []) - c.Authenticator.admin_users.extend(staff_user_ids) - - # Check what authenticator class is set. If it's "github", we assume - # GitHub Orgs/Teams is being used for auth and unset allowed_users - # so valid members are not refused access. - # FIXME: This should be handled in basehub's schema validation file - # so that we get useful feedback about config. But at time of writing, - # it doesn't have one! Issue to track the creation of such files is: - # https://github.com/2i2c-org/infrastructure/issues/937 - authenticator_class = get_config("hub.config.JupyterHub.authenticator_class") - if authenticator_class == "github" and c.Authenticator.allowed_users: - print("WARNING: hub.config.JupyterHub.authenticator_class was set to github and c.Authenticator.allowed_users was set, custom 2i2c jupyterhub config is now resetting allowed_users to an empty set.") - c.Authenticator.allowed_users = set() - 05-add-docs-service-if-enabled: | - from z2jh import get_config - - if get_config("custom.docs_service.enabled"): - c.JupyterHub.services.append({"name": "docs", "url": "http://docs-service"}) - 06-gh-teams: | - from textwrap import dedent - from tornado import gen, web - from oauthenticator.github import GitHubOAuthenticator - - # Make a copy of the original profile_list, as that is the data we will work with - original_profile_list = c.KubeSpawner.profile_list - - # This has to be a gen.coroutine, not async def! Kubespawner uses gen.maybe_future to - # run this, and that only seems to recognize tornado coroutines, not async functions! - # We can convert this to async def once that has been fixed upstream. - @gen.coroutine - def custom_profile_list(spawner): - """ - Dynamically set allowed list of user profiles based on GitHub teams user is part of. - - Adds a 'allowed_teams' key to profile_list, with a list of GitHub teams (of the form - org-name:team-name) for which the profile is made available. - - If the user isn't part of any team whose membership grants them access to even a single - profile, they aren't allowed to start any servers. - """ - # Only apply to GitHub Authenticator - if not isinstance(spawner.authenticator, GitHubOAuthenticator): - return original_profile_list - - # If populate_teams_in_auth_state is not set, github teams are not fetched - # So we just don't do any of this filtering, and let anyone into everything - if spawner.authenticator.populate_teams_in_auth_state == False: - return original_profile_list - - auth_state = yield spawner.user.get_auth_state() - - if not auth_state or "teams" not in auth_state: - if spawner.user.name == 'deployment-service-check': - # For our hub deployer health checker, ignore all this logic - print("Ignoring allowed_teams check for deployment-service-check") - return original_profile_list - print(f"User {spawner.user.name} does not have any auth_state set") - raise web.HTTPError(403) - - # Make a list of team names of form org-name:team-name - # This is the same syntax used by allowed_organizations traitlet of GitHubOAuthenticator - teams = set([f'{team_info["organization"]["login"]}:{team_info["slug"]}' for team_info in auth_state["teams"]]) - - allowed_profiles = [] - - for profile in original_profile_list: - # Keep the profile is the user is part of *any* team listed in allowed_teams - # If allowed_teams is empty or not set, it'll not be accessible to *anyone* - if set(profile.get('allowed_teams', [])) & teams: - allowed_profiles.append(profile) - print(f"Allowing profile {profile['display_name']} for user {spawner.user.name}") - else: - print(f"Dropping profile {profile['display_name']} for user {spawner.user.name}") - - if len(allowed_profiles) == 0: - # If no profiles are allowed, user should not be able to spawn anything! - # If we don't explicitly stop this, user will be logged into the 'default' settings - # set in singleuser, without any profile overrides. Not desired behavior - # FIXME: User doesn't actually see this error message, just the generic 403. - error_msg = dedent(f""" - Your GitHub team membership is insufficient to launch any server profiles. - - GitHub teams you are a member of that this JupyterHub knows about are {', '.join(teams)}. - - If you are part of additional teams, log out of this JupyterHub and log back in to refresh that information. - """) - raise web.HTTPError(403, error_msg) - - return allowed_profiles - - # Only set this customized profile_list *if* we already have a profile_list set - # otherwise, we'll show users a blank server options form and they won't be able to - # start their server - if c.KubeSpawner.profile_list: - # Customize list of profiles dynamically, rather than override options form. - # This is more secure, as users can't override the options available to them via the hub API - c.KubeSpawner.profile_list = custom_profile_list + # 01-custom-theme: | + # from z2jh import get_config + # c.JupyterHub.template_paths = ['/usr/local/share/jupyterhub/custom_templates/'] + + # c.JupyterHub.template_vars = { + # 'custom': get_config('custom.homepage.templateVars') + # } + # 02-custom-admin: | + # from z2jh import get_config + # from kubespawner import KubeSpawner + # from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin + + # class CustomSpawner(ConfiguratorSpawnerMixin, KubeSpawner): + # def start(self, *args, **kwargs): + # custom_admin = get_config('custom.singleuserAdmin', {}) + # if custom_admin and self.user.admin: + # extra_init_containers = custom_admin.get('initContainers', []) + # extra_volume_mounts = custom_admin.get('extraVolumeMounts', []) + + # self.init_containers += [container for container in extra_init_containers if container not in self.init_containers] + # self.volume_mounts += [volume for volume in extra_volume_mounts if volume not in self.volume_mounts] + + # return super().start(*args, **kwargs) + + + # c.JupyterHub.spawner_class = CustomSpawner + # 03-cloud-storage-bucket: | + # from z2jh import get_config + # cloud_resources = get_config('custom.cloudResources') + # scratch_bucket = cloud_resources['scratchBucket'] + # import os + + # if scratch_bucket['enabled']: + # # FIXME: Support other providers too + # assert cloud_resources['provider'] == 'gcp' + # project_id = cloud_resources['gcp']['projectId'] + + # release = os.environ['HELM_RELEASE_NAME'] + # bucket_protocol = 'gcs' + # bucket_name = f'{project_id}-{release}-scratch-bucket' + # env = { + # 'SCRATCH_BUCKET_PROTOCOL': bucket_protocol, + # # Matches "daskhub.scratchBUcket.name" helm template + # 'SCRATCH_BUCKET_NAME': bucket_name, + # # Use k8s syntax of $(ENV_VAR) to substitute env vars dynamically in other env vars + # 'SCRATCH_BUCKET': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', + # 'PANGEO_SCRATCH': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', + # } + + # c.KubeSpawner.environment.update(env) + # 04-2i2c-add-staff-user-ids-to-admin-users: | + # from z2jh import get_config + # add_staff_user_ids_to_admin_users = get_config("custom.2i2c.add_staff_user_ids_to_admin_users", False) + + # if add_staff_user_ids_to_admin_users: + # user_id_type = get_config("custom.2i2c.add_staff_user_ids_of_type") + # staff_user_ids = get_config(f"custom.2i2c.staff_{user_id_type}_ids", []) + # c.Authenticator.admin_users.extend(staff_user_ids) + + # # Check what authenticator class is set. If it's "github", we assume + # # GitHub Orgs/Teams is being used for auth and unset allowed_users + # # so valid members are not refused access. + # # FIXME: This should be handled in basehub's schema validation file + # # so that we get useful feedback about config. But at time of writing, + # # it doesn't have one! Issue to track the creation of such files is: + # # https://github.com/2i2c-org/infrastructure/issues/937 + # authenticator_class = get_config("hub.config.JupyterHub.authenticator_class") + # if authenticator_class == "github" and c.Authenticator.allowed_users: + # print("WARNING: hub.config.JupyterHub.authenticator_class was set to github and c.Authenticator.allowed_users was set, custom 2i2c jupyterhub config is now resetting allowed_users to an empty set.") + # c.Authenticator.allowed_users = set() + # 05-add-docs-service-if-enabled: | + # from z2jh import get_config + + # if get_config("custom.docs_service.enabled"): + # c.JupyterHub.services.append({"name": "docs", "url": "http://docs-service"}) + # 06-gh-teams: | + # from textwrap import dedent + # from tornado import gen, web + # from oauthenticator.github import GitHubOAuthenticator + + # # Make a copy of the original profile_list, as that is the data we will work with + # original_profile_list = c.KubeSpawner.profile_list + + # # This has to be a gen.coroutine, not async def! Kubespawner uses gen.maybe_future to + # # run this, and that only seems to recognize tornado coroutines, not async functions! + # # We can convert this to async def once that has been fixed upstream. + # @gen.coroutine + # def custom_profile_list(spawner): + # """ + # Dynamically set allowed list of user profiles based on GitHub teams user is part of. + + # Adds a 'allowed_teams' key to profile_list, with a list of GitHub teams (of the form + # org-name:team-name) for which the profile is made available. + + # If the user isn't part of any team whose membership grants them access to even a single + # profile, they aren't allowed to start any servers. + # """ + # # Only apply to GitHub Authenticator + # if not isinstance(spawner.authenticator, GitHubOAuthenticator): + # return original_profile_list + + # # If populate_teams_in_auth_state is not set, github teams are not fetched + # # So we just don't do any of this filtering, and let anyone into everything + # if spawner.authenticator.populate_teams_in_auth_state == False: + # return original_profile_list + + # auth_state = yield spawner.user.get_auth_state() + + # if not auth_state or "teams" not in auth_state: + # if spawner.user.name == 'deployment-service-check': + # # For our hub deployer health checker, ignore all this logic + # print("Ignoring allowed_teams check for deployment-service-check") + # return original_profile_list + # print(f"User {spawner.user.name} does not have any auth_state set") + # raise web.HTTPError(403) + + # # Make a list of team names of form org-name:team-name + # # This is the same syntax used by allowed_organizations traitlet of GitHubOAuthenticator + # teams = set([f'{team_info["organization"]["login"]}:{team_info["slug"]}' for team_info in auth_state["teams"]]) + + # allowed_profiles = [] + + # for profile in original_profile_list: + # # Keep the profile is the user is part of *any* team listed in allowed_teams + # # If allowed_teams is empty or not set, it'll not be accessible to *anyone* + # if set(profile.get('allowed_teams', [])) & teams: + # allowed_profiles.append(profile) + # print(f"Allowing profile {profile['display_name']} for user {spawner.user.name}") + # else: + # print(f"Dropping profile {profile['display_name']} for user {spawner.user.name}") + + # if len(allowed_profiles) == 0: + # # If no profiles are allowed, user should not be able to spawn anything! + # # If we don't explicitly stop this, user will be logged into the 'default' settings + # # set in singleuser, without any profile overrides. Not desired behavior + # # FIXME: User doesn't actually see this error message, just the generic 403. + # error_msg = dedent(f""" + # Your GitHub team membership is insufficient to launch any server profiles. + + # GitHub teams you are a member of that this JupyterHub knows about are {', '.join(teams)}. + + # If you are part of additional teams, log out of this JupyterHub and log back in to refresh that information. + # """) + # raise web.HTTPError(403, error_msg) + + # return allowed_profiles + + # # Only set this customized profile_list *if* we already have a profile_list set + # # otherwise, we'll show users a blank server options form and they won't be able to + # # start their server + # if c.KubeSpawner.profile_list: + # # Customize list of profiles dynamically, rather than override options form. + # # This is more secure, as users can't override the options available to them via the hub API + # c.KubeSpawner.profile_list = custom_profile_list dask-gateway: #=== VALUES BELOW HERE ARE COPIED FROM DASKHUB VALUES AND SHOULD BE UPDATED ===# From 00da27ef9c28ffa681544af54c96f2dd92117ec9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Jun 2022 16:20:36 +0000 Subject: [PATCH 10/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- helm-charts/binderhub/values.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index 7a3330183e..b3d2450549 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -280,7 +280,6 @@ binderhub: # return super().start(*args, **kwargs) - # c.JupyterHub.spawner_class = CustomSpawner # 03-cloud-storage-bucket: | # from z2jh import get_config From 6543a9ca329efddf6f35e184305c5015c7d2f8ad Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 8 Jun 2022 17:55:13 +0100 Subject: [PATCH 11/20] Remove singleuser.serviceAccountName --- helm-charts/binderhub/values.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index b3d2450549..89ef6af2f0 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -77,7 +77,6 @@ binderhub: # without an explicit identity # FIXME: Provide an explicit identity for users instead blockWithIptables: false - serviceAccountName: user-sa extraEnv: # About DASK_ prefixed variables we set: # From 54bfd7b4ea2528e2365d313094894eeb889293f0 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 15 Jun 2022 09:53:43 +0100 Subject: [PATCH 12/20] Revert "Temporarily skip validation when deploying" This reverts commit 69f712d76b854060641719eef586befb298c916f. --- deployer/deploy_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/deploy_actions.py b/deployer/deploy_actions.py index 93a67ef1ef..1bf7753a6a 100644 --- a/deployer/deploy_actions.py +++ b/deployer/deploy_actions.py @@ -181,7 +181,7 @@ def deploy(cluster_name, hub_name, config_path): Deploy one or more hubs in a given cluster """ validate_cluster_config(cluster_name) - # validate_hub_config(cluster_name, hub_name) + validate_hub_config(cluster_name, hub_name) assert_single_auth_method_enabled(cluster_name, hub_name) with get_decrypted_file(config_path) as decrypted_file_path: From bbc5a559767ad17fafcf01028867ff94b6db5f2d Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 15 Jun 2022 14:35:30 +0100 Subject: [PATCH 13/20] Update values to resemble the original Pangeo Binder's https://github.com/pangeo-data/pangeo-binder/blob/staging/pangeo-binder/values.yaml --- helm-charts/binderhub/values.yaml | 225 ++++++------------------------ 1 file changed, 42 insertions(+), 183 deletions(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index 89ef6af2f0..3d6a0b8ed9 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -3,6 +3,12 @@ binderhub: BinderHub: use_registry: true build_image: "quay.io/jupyterhub/repo2docker:2022.02.0-30.gc4e01b3" + per_repo_quota: 150 + template_path: /etc/binderhub/custom/templates + extra_static_path: /etc/binderhub/custom/static + extra_static_url_prefix: /extra_static/ + about_message: | +

binder.pangeo.io is public infrastructure operated by the 2i2c team.

service: type: ClusterIP ingress: @@ -11,31 +17,44 @@ binderhub: nginx.ingress.kubernetes.io/proxy-body-size: 256m kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod + initContainers: + - name: git-clone-templates + image: alpine/git + args: + - clone + - --single-branch + - --branch=master + - --depth=1 + - -- + - https://github.com/pangeo-data/pangeo-custom-binderhub-templates.git + - /etc/binderhub/custom + securityContext: + runAsUser: 0 + volumeMounts: + - name: custom-templates + mountPath: /etc/binderhub/custom + extraVolumes: + - name: custom-templates + emptyDir: {} + extraVolumeMounts: + - name: custom-templates + mountPath: /etc/binderhub/custom + dind: + enabled: true + daemonset: + image: + name: docker + tag: 19.03.5-dind + imageCleaner: + enabled: true + # when 80% of inodes are used, + # cull images until only 40% are used. + imageGCThresholdHigh: 80 + imageGCThresholdLow: 40 + host: + enabled: false jupyterhub: - #=== VALUES BELOW HERE ARE COPIED FROM DASKHUB VALUES AND SHOULD BE UPDATED ===# - #=== IF DASKHUB CHANGES ===# - custom: - 2i2c: - # Should 2i2c engineering staff user IDs be injected to the admin_users - # configuration of the JupyterHub's authenticator by our custom - # jupyterhub_config.py snippet as declared in hub.extraConfig? - add_staff_user_ids_to_admin_users: false - add_staff_user_ids_of_type: "" - staff_github_ids: - - choldgraf - - consideRatio - - damianavila - - GeorgianaElena - - sgibson91 - - yuvipanda - staff_google_ids: - - choldgraf@2i2c.org - - erik@2i2c.org - - damianavila@2i2c.org - - georgianaelena@2i2c.org - - sgibson@2i2c.org - - yuvipanda@2i2c.org ingress: enabled: true annotations: @@ -190,13 +209,6 @@ binderhub: securityContext: # Explicitly disallow setuid binaries from working inside the container allowPrivilegeEscalation: false - Authenticator: - # Don't allow test username to login into the hub - # The test service will still be able to create this hub username - # and start their server. - # Ref: https://github.com/2i2c-org/meta/issues/321 - blocked_users: - - deployment-service-check services: dask-gateway: # Don't display a dask-gateway entry under 'services', @@ -255,159 +267,6 @@ binderhub: break else: print("dask-gateway service not found. Did you set jupyterhub.hub.services.dask-gateway.apiToken?") - # 01-custom-theme: | - # from z2jh import get_config - # c.JupyterHub.template_paths = ['/usr/local/share/jupyterhub/custom_templates/'] - - # c.JupyterHub.template_vars = { - # 'custom': get_config('custom.homepage.templateVars') - # } - # 02-custom-admin: | - # from z2jh import get_config - # from kubespawner import KubeSpawner - # from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin - - # class CustomSpawner(ConfiguratorSpawnerMixin, KubeSpawner): - # def start(self, *args, **kwargs): - # custom_admin = get_config('custom.singleuserAdmin', {}) - # if custom_admin and self.user.admin: - # extra_init_containers = custom_admin.get('initContainers', []) - # extra_volume_mounts = custom_admin.get('extraVolumeMounts', []) - - # self.init_containers += [container for container in extra_init_containers if container not in self.init_containers] - # self.volume_mounts += [volume for volume in extra_volume_mounts if volume not in self.volume_mounts] - - # return super().start(*args, **kwargs) - - # c.JupyterHub.spawner_class = CustomSpawner - # 03-cloud-storage-bucket: | - # from z2jh import get_config - # cloud_resources = get_config('custom.cloudResources') - # scratch_bucket = cloud_resources['scratchBucket'] - # import os - - # if scratch_bucket['enabled']: - # # FIXME: Support other providers too - # assert cloud_resources['provider'] == 'gcp' - # project_id = cloud_resources['gcp']['projectId'] - - # release = os.environ['HELM_RELEASE_NAME'] - # bucket_protocol = 'gcs' - # bucket_name = f'{project_id}-{release}-scratch-bucket' - # env = { - # 'SCRATCH_BUCKET_PROTOCOL': bucket_protocol, - # # Matches "daskhub.scratchBUcket.name" helm template - # 'SCRATCH_BUCKET_NAME': bucket_name, - # # Use k8s syntax of $(ENV_VAR) to substitute env vars dynamically in other env vars - # 'SCRATCH_BUCKET': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', - # 'PANGEO_SCRATCH': f'{bucket_protocol}://{bucket_name}/$(JUPYTERHUB_USER)', - # } - - # c.KubeSpawner.environment.update(env) - # 04-2i2c-add-staff-user-ids-to-admin-users: | - # from z2jh import get_config - # add_staff_user_ids_to_admin_users = get_config("custom.2i2c.add_staff_user_ids_to_admin_users", False) - - # if add_staff_user_ids_to_admin_users: - # user_id_type = get_config("custom.2i2c.add_staff_user_ids_of_type") - # staff_user_ids = get_config(f"custom.2i2c.staff_{user_id_type}_ids", []) - # c.Authenticator.admin_users.extend(staff_user_ids) - - # # Check what authenticator class is set. If it's "github", we assume - # # GitHub Orgs/Teams is being used for auth and unset allowed_users - # # so valid members are not refused access. - # # FIXME: This should be handled in basehub's schema validation file - # # so that we get useful feedback about config. But at time of writing, - # # it doesn't have one! Issue to track the creation of such files is: - # # https://github.com/2i2c-org/infrastructure/issues/937 - # authenticator_class = get_config("hub.config.JupyterHub.authenticator_class") - # if authenticator_class == "github" and c.Authenticator.allowed_users: - # print("WARNING: hub.config.JupyterHub.authenticator_class was set to github and c.Authenticator.allowed_users was set, custom 2i2c jupyterhub config is now resetting allowed_users to an empty set.") - # c.Authenticator.allowed_users = set() - # 05-add-docs-service-if-enabled: | - # from z2jh import get_config - - # if get_config("custom.docs_service.enabled"): - # c.JupyterHub.services.append({"name": "docs", "url": "http://docs-service"}) - # 06-gh-teams: | - # from textwrap import dedent - # from tornado import gen, web - # from oauthenticator.github import GitHubOAuthenticator - - # # Make a copy of the original profile_list, as that is the data we will work with - # original_profile_list = c.KubeSpawner.profile_list - - # # This has to be a gen.coroutine, not async def! Kubespawner uses gen.maybe_future to - # # run this, and that only seems to recognize tornado coroutines, not async functions! - # # We can convert this to async def once that has been fixed upstream. - # @gen.coroutine - # def custom_profile_list(spawner): - # """ - # Dynamically set allowed list of user profiles based on GitHub teams user is part of. - - # Adds a 'allowed_teams' key to profile_list, with a list of GitHub teams (of the form - # org-name:team-name) for which the profile is made available. - - # If the user isn't part of any team whose membership grants them access to even a single - # profile, they aren't allowed to start any servers. - # """ - # # Only apply to GitHub Authenticator - # if not isinstance(spawner.authenticator, GitHubOAuthenticator): - # return original_profile_list - - # # If populate_teams_in_auth_state is not set, github teams are not fetched - # # So we just don't do any of this filtering, and let anyone into everything - # if spawner.authenticator.populate_teams_in_auth_state == False: - # return original_profile_list - - # auth_state = yield spawner.user.get_auth_state() - - # if not auth_state or "teams" not in auth_state: - # if spawner.user.name == 'deployment-service-check': - # # For our hub deployer health checker, ignore all this logic - # print("Ignoring allowed_teams check for deployment-service-check") - # return original_profile_list - # print(f"User {spawner.user.name} does not have any auth_state set") - # raise web.HTTPError(403) - - # # Make a list of team names of form org-name:team-name - # # This is the same syntax used by allowed_organizations traitlet of GitHubOAuthenticator - # teams = set([f'{team_info["organization"]["login"]}:{team_info["slug"]}' for team_info in auth_state["teams"]]) - - # allowed_profiles = [] - - # for profile in original_profile_list: - # # Keep the profile is the user is part of *any* team listed in allowed_teams - # # If allowed_teams is empty or not set, it'll not be accessible to *anyone* - # if set(profile.get('allowed_teams', [])) & teams: - # allowed_profiles.append(profile) - # print(f"Allowing profile {profile['display_name']} for user {spawner.user.name}") - # else: - # print(f"Dropping profile {profile['display_name']} for user {spawner.user.name}") - - # if len(allowed_profiles) == 0: - # # If no profiles are allowed, user should not be able to spawn anything! - # # If we don't explicitly stop this, user will be logged into the 'default' settings - # # set in singleuser, without any profile overrides. Not desired behavior - # # FIXME: User doesn't actually see this error message, just the generic 403. - # error_msg = dedent(f""" - # Your GitHub team membership is insufficient to launch any server profiles. - - # GitHub teams you are a member of that this JupyterHub knows about are {', '.join(teams)}. - - # If you are part of additional teams, log out of this JupyterHub and log back in to refresh that information. - # """) - # raise web.HTTPError(403, error_msg) - - # return allowed_profiles - - # # Only set this customized profile_list *if* we already have a profile_list set - # # otherwise, we'll show users a blank server options form and they won't be able to - # # start their server - # if c.KubeSpawner.profile_list: - # # Customize list of profiles dynamically, rather than override options form. - # # This is more secure, as users can't override the options available to them via the hub API - # c.KubeSpawner.profile_list = custom_profile_list dask-gateway: #=== VALUES BELOW HERE ARE COPIED FROM DASKHUB VALUES AND SHOULD BE UPDATED ===# From 2d46bc2e2cfcdd51c02d813a52091dcd49cdf6fd Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Wed, 15 Jun 2022 16:16:06 +0100 Subject: [PATCH 14/20] Set dummy dask api token for binderhub too --- deployer/config_validation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deployer/config_validation.py b/deployer/config_validation.py index 834be503bf..e7d6160415 100644 --- a/deployer/config_validation.py +++ b/deployer/config_validation.py @@ -113,7 +113,9 @@ def validate_hub_config(cluster_name, hub_name): # Workaround the current requirement for dask-gateway 0.9.0 to have a # JupyterHub api-token specified, for updates if this workaround can be # removed, see https://github.com/dask/dask-gateway/issues/473. - if hub.spec["helm_chart"] == "daskhub": + if (hub.spec["helm_chart"] == "daskhub") or ( + hub.spec["helm_chart"] == "binderhub" + ): cmd.append("--set=dask-gateway.gateway.auth.jupyterhub.apiToken=dummy") try: subprocess.check_output(cmd, text=True) From a31a2d5adbc6b72c11b0b2487c9579e281ce36dd Mon Sep 17 00:00:00 2001 From: Sarah Gibson <44771837+sgibson91@users.noreply.github.com> Date: Thu, 16 Jun 2022 10:39:43 +0100 Subject: [PATCH 15/20] Update deployer/config_validation.py Co-authored-by: Yuvi Panda --- deployer/config_validation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deployer/config_validation.py b/deployer/config_validation.py index e7d6160415..f21ccce3a5 100644 --- a/deployer/config_validation.py +++ b/deployer/config_validation.py @@ -113,9 +113,7 @@ def validate_hub_config(cluster_name, hub_name): # Workaround the current requirement for dask-gateway 0.9.0 to have a # JupyterHub api-token specified, for updates if this workaround can be # removed, see https://github.com/dask/dask-gateway/issues/473. - if (hub.spec["helm_chart"] == "daskhub") or ( - hub.spec["helm_chart"] == "binderhub" - ): + if (hub.spec["helm_chart"] in ("daskhub", "binderhub")): cmd.append("--set=dask-gateway.gateway.auth.jupyterhub.apiToken=dummy") try: subprocess.check_output(cmd, text=True) From 6c86fc69e5ec4d3e06a17d0e2bd0e2442ad11e9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:40:02 +0000 Subject: [PATCH 16/20] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- deployer/config_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/config_validation.py b/deployer/config_validation.py index f21ccce3a5..4b5b18a151 100644 --- a/deployer/config_validation.py +++ b/deployer/config_validation.py @@ -113,7 +113,7 @@ def validate_hub_config(cluster_name, hub_name): # Workaround the current requirement for dask-gateway 0.9.0 to have a # JupyterHub api-token specified, for updates if this workaround can be # removed, see https://github.com/dask/dask-gateway/issues/473. - if (hub.spec["helm_chart"] in ("daskhub", "binderhub")): + if hub.spec["helm_chart"] in ("daskhub", "binderhub"): cmd.append("--set=dask-gateway.gateway.auth.jupyterhub.apiToken=dummy") try: subprocess.check_output(cmd, text=True) From 0ffba14f2411f4d5e756a675bf8bffa0c1147e66 Mon Sep 17 00:00:00 2001 From: Sarah Gibson <44771837+sgibson91@users.noreply.github.com> Date: Thu, 16 Jun 2022 10:40:42 +0100 Subject: [PATCH 17/20] remove dind config, inherit from upstream binderhub chart upstream has a much more recent tag for the dind image --- helm-charts/binderhub/values.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index 3d6a0b8ed9..d5197686d8 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -41,10 +41,6 @@ binderhub: mountPath: /etc/binderhub/custom dind: enabled: true - daemonset: - image: - name: docker - tag: 19.03.5-dind imageCleaner: enabled: true # when 80% of inodes are used, From fe9cd966215f0d137efc819203cd1dabd59fb483 Mon Sep 17 00:00:00 2001 From: Sarah Gibson Date: Thu, 16 Jun 2022 13:24:26 +0100 Subject: [PATCH 18/20] Don't pin traefik image --- helm-charts/binderhub/values.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index d5197686d8..aaa05f72f5 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -72,8 +72,6 @@ binderhub: limits: memory: 1Gi traefik: - image: - tag: v2.4.8 nodeSelector: hub.jupyter.org/node-purpose: core resources: From c0eefeb7a33209b5c2dd711b302957c36434042e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 21 Jun 2022 12:51:34 -0700 Subject: [PATCH 19/20] Add userServiceAccount to binderhub Used to provide access to cloud resources --- helm-charts/binderhub/templates/user-sa.yaml | 7 ++++++ helm-charts/binderhub/values.schema.yaml | 24 ++++++++++++++++++++ helm-charts/binderhub/values.yaml | 3 +++ 3 files changed, 34 insertions(+) create mode 100644 helm-charts/binderhub/templates/user-sa.yaml diff --git a/helm-charts/binderhub/templates/user-sa.yaml b/helm-charts/binderhub/templates/user-sa.yaml new file mode 100644 index 0000000000..21255905a7 --- /dev/null +++ b/helm-charts/binderhub/templates/user-sa.yaml @@ -0,0 +1,7 @@ +{{ if .Values.userServiceAccount.enabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: {{ .Values.userServiceAccount.annotations | toJson}} + name: user-sa +{{- end }} \ No newline at end of file diff --git a/helm-charts/binderhub/values.schema.yaml b/helm-charts/binderhub/values.schema.yaml index d303a0f033..0bce03c743 100644 --- a/helm-charts/binderhub/values.schema.yaml +++ b/helm-charts/binderhub/values.schema.yaml @@ -14,7 +14,31 @@ required: - binderhub - dask-gateway - global + - userServiceAccount properties: + userServiceAccount: + type: object + additionalProperties: false + required: + - enabled + properties: + enabled: + type: boolean + description: | + Enables creation of a Service Account for use by notebook & dask pods. + + Config must still be set for notebook and dask pods to actually use + this service account, which is named user-sa. + annotations: + type: object + additionalProperties: true + description: | + Dictionary of annotations that can be applied to the service account. + + When used with GKE and Workload Identity, you need to set + the annotation with key "iam.gke.io/gcp-service-account" to the + email address of the Google Service Account whose credentials it + should have. # binderhub is a dependent helm chart, we rely on its schema validation for # values passed to it and are not imposing restrictions on them in this helm # chart. diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index fdf706d73e..793b016818 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -1,3 +1,5 @@ +userServiceAccount: + enabled: true binderhub: config: BinderHub: @@ -80,6 +82,7 @@ binderhub: limits: memory: 1Gi singleuser: + serviceAccountName: user-sa # Almost everyone using dask by default wants JupyterLab defaultUrl: /lab extraLabels: From 461f08a7ba61cdc13879bd1e00ff4b7716f609a4 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 21 Jun 2022 13:12:53 -0700 Subject: [PATCH 20/20] Build user images only on user nodes Avoids extra dind deployments on core nodes and dask-gateway nodes --- helm-charts/binderhub/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helm-charts/binderhub/values.yaml b/helm-charts/binderhub/values.yaml index fdf706d73e..9b406845e2 100644 --- a/helm-charts/binderhub/values.yaml +++ b/helm-charts/binderhub/values.yaml @@ -9,6 +9,9 @@ binderhub: extra_static_url_prefix: /extra_static/ about_message: |

binder.pangeo.io is public infrastructure operated by the 2i2c team.

+ build_node_selector: + # Build user images only on user nodes, not dask or core nodes + hub.jupyter.org/node-purpose: user service: type: ClusterIP ingress: