Skip to content

Commit

Permalink
Merge pull request #1404 from sgibson91/deploy-test-binder
Browse files Browse the repository at this point in the history
  • Loading branch information
sgibson91 authored Jun 22, 2022
2 parents fb044f8 + cdcdd0e commit 1f76a48
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 278 deletions.
4 changes: 4 additions & 0 deletions config/clusters/2i2c/binder.values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
binderhub:
config:
BinderHub:
image_prefix: "sgibson91/2i2c-binder-staging-"
11 changes: 11 additions & 0 deletions config/clusters/2i2c/cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions config/clusters/2i2c/enc-binder.secret.values.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion deployer/config_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -109,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"] == "daskhub":
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)
Expand Down
222 changes: 131 additions & 91 deletions deployer/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,90 +31,127 @@ 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']}"}
},
"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']}"],
}
],
}
],
"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,
},
}
}
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",
},
"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",
},
],
{
"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):
Expand Down Expand Up @@ -156,16 +193,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
Expand All @@ -174,7 +201,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()
Expand All @@ -185,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):
Expand Down
7 changes: 7 additions & 0 deletions helm-charts/binderhub/templates/user-sa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{ if .Values.userServiceAccount.enabled -}}
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {{ .Values.userServiceAccount.annotations | toJson}}
name: user-sa
{{- end }}
24 changes: 24 additions & 0 deletions helm-charts/binderhub/values.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 1f76a48

Please sign in to comment.