Skip to content
This repository has been archived by the owner on Mar 17, 2023. It is now read-only.

Commit

Permalink
Merge branch 'release/3.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Nov 16, 2019
2 parents 8b8250d + c0835e0 commit a294175
Show file tree
Hide file tree
Showing 13 changed files with 819 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .s2i/bin/assemble
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ npm install -g configurable-http-proxy
cp /tmp/src/jupyterhub_config.py /opt/app-root/etc/
cp /tmp/src/jupyterhub_config.sh /opt/app-root/etc/

cp /tmp/src/jupyterhub_config-*.py /opt/app-root/etc/
cp /tmp/src/jupyterhub_config-*.sh /opt/app-root/etc/

# This S2I assemble script is only used when creating the custom image.
# For when running the image, or using it as a S2I builder, we use a second
# set of custom S2I scripts. We now need to move these into the correct
Expand Down
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ To make it easier to deploy JupyterHub in OpenShift, templates are provided. To
oc apply -f https://raw.githubusercontent.com/jupyter-on-openshift/jupyterhub-quickstart/master/templates/jupyterhub-builder.json
oc apply -f https://raw.githubusercontent.com/jupyter-on-openshift/jupyterhub-quickstart/master/templates/jupyterhub-deployer.json
oc apply -f https://raw.githubusercontent.com/jupyter-on-openshift/jupyterhub-quickstart/master/templates/jupyterhub-quickstart.json
oc apply -f https://raw.githubusercontent.com/jupyter-on-openshift/jupyterhub-quickstart/master/templates/jupyterhub-workspace.json
```

This should result in the creation of the templates ``jupyterhub-builder``, ``jupyterhub-deployer`` and ``jupyterhub-quickstart``.
This should result in the creation of the templates ``jupyterhub-builder``, ``jupyterhub-deployer``, ``jupyterhub-quickstart`` and ``jupyterhub-workspace``.

Creating the JupyterHub Deployment
----------------------------------
Expand Down Expand Up @@ -438,3 +439,43 @@ c.JupyterHub.services = [
```

The ``cull-idle-servers`` program is provided with the JupyterHub image. Adjust the value for the timeout argument as necessary.

Multi User Developer Workspace
------------------------------

The ``jupyterhub-workspace`` template combines a number of the above configuration options into one template. These include:

* Authentication of users using OpenShift cluster OAuth provider.
* Optional specification of whitelisted users, including those who are admins.
* Optional allocation of a persistent storage volume for each user.
* Optional culling of idle sessions.

Note that the template can only be used with Jupyter notebook images based on the ``s2i-minimal-notebook`` images. You can use official images from the Jupyter Project.

Also, the ``jupyterhub-workspace`` template can only be deployed by a cluster admin, as it needs to create an ``oauthclient`` resource definition, which requires cluster admin access.

To deploy the template and provide persistent storage and idle session culling you can use:

```
oc new-app --template jupyterhub-workspace --param VOLUME_SIZE=1Gi --param IDLE_TIMEOUT=3600
```

To delete the deployment first use:

```
oc delete all,configmap,pvc,serviceaccount,rolebinding --selector app=jupyterhub
```

You then need to delete the ``oauthclient`` resource. Because this is a global resource, verify you are deleting the correct resource first by running:

```
oc get oauthclient --selector app=jupyterhub
```

If it is correct, then delete it using:

```
oc delete oauthclient --selector app=jupyterhub
```

If there is more than one resource matching the label selector, delete by name the one corresponding to the project you created the deployment in. The project name will be part of the resource name.
4 changes: 2 additions & 2 deletions build-configs/jupyterhub.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"type": "Git",
"git": {
"uri": "https://github.com/jupyter-on-openshift/jupyterhub-quickstart.git",
"ref": "3.3.1"
"ref": "3.4.0"
}
},
"strategy": {
Expand All @@ -55,7 +55,7 @@
"output": {
"to": {
"kind": "ImageStreamTag",
"name": "jupyterhub:3.3.1"
"name": "jupyterhub:3.4.0"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions image-streams/jupyterhub.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
},
"tags": [
{
"name": "3.3.1",
"name": "3.4.0",
"from": {
"kind": "DockerImage",
"name": "quay.io/jupyteronopenshift/jupyterhub:3.3.1"
"name": "quay.io/jupyteronopenshift/jupyterhub:3.4.0"
}
}
]
Expand Down
154 changes: 154 additions & 0 deletions jupyterhub_config-workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Authenticate users against OpenShift OAuth provider.

c.JupyterHub.authenticator_class = "openshift"

from oauthenticator.openshift import OpenShiftOAuthenticator
OpenShiftOAuthenticator.scope = ['user:full']

client_id = '%s-%s-users' % (application_name, namespace)
client_secret = os.environ['OAUTH_CLIENT_SECRET']

c.OpenShiftOAuthenticator.client_id = client_id
c.OpenShiftOAuthenticator.client_secret = client_secret
c.Authenticator.enable_auth_state = True

c.CryptKeeper.keys = [ client_secret.encode('utf-8') ]

c.OpenShiftOAuthenticator.oauth_callback_url = (
'https://%s/hub/oauth_callback' % public_hostname)

# Add any additional JupyterHub configuration settings.

c.KubeSpawner.extra_labels = {
'spawner': 'workspace',
'class': 'session',
'user': '{username}'
}

# Set up list of registered users and any users nominated as admins.

if os.path.exists('/opt/app-root/configs/admin_users.txt'):
with open('/opt/app-root/configs/admin_users.txt') as fp:
content = fp.read().strip()
if content:
c.Authenticator.admin_users = set(content.split())

if os.path.exists('/opt/app-root/configs/user_whitelist.txt'):
with open('/opt/app-root/configs/user_whitelist.txt') as fp:
c.Authenticator.whitelist = set(fp.read().strip().split())

# For workshops we provide each user with a persistent volume so they
# don't loose their work. This is mounted on /opt/app-root, so we need
# to copy the contents from the image into the persistent volume the
# first time using an init container.

volume_size = os.environ.get('JUPYTERHUB_VOLUME_SIZE')

if volume_size:
c.KubeSpawner.pvc_name_template = c.KubeSpawner.pod_name_template

c.KubeSpawner.storage_pvc_ensure = True

c.KubeSpawner.storage_capacity = volume_size

c.KubeSpawner.storage_access_modes = ['ReadWriteOnce']

c.KubeSpawner.volumes.extend([
{
'name': 'data',
'persistentVolumeClaim': {
'claimName': c.KubeSpawner.pvc_name_template
}
}
])

c.KubeSpawner.volume_mounts.extend([
{
'name': 'data',
'mountPath': '/opt/app-root',
'subPath': 'workspace'
}
])

c.KubeSpawner.init_containers.extend([
{
'name': 'setup-volume',
'image': '%s' % c.KubeSpawner.image_spec,
'command': [
'/opt/app-root/bin/setup-volume.sh',
'/opt/app-root',
'/mnt/workspace'
],
"resources": {
"limits": {
"memory": os.environ.get('NOTEBOOK_MEMORY', '128Mi')
},
"requests": {
"memory": os.environ.get('NOTEBOOK_MEMORY', '128Mi')
}
},
'volumeMounts': [
{
'name': 'data',
'mountPath': '/mnt'
}
]
}
])

# Make modifications to pod based on user and type of session.

from tornado import gen

@gen.coroutine
def modify_pod_hook(spawner, pod):
pod.spec.automount_service_account_token = True

# Grab the OpenShift user access token from the login state.

auth_state = yield spawner.user.get_auth_state()
access_token = auth_state['access_token']

# Set the session access token from the OpenShift login.

pod.spec.containers[0].env.append(
dict(name='OPENSHIFT_TOKEN', value=access_token))

# See if a template for the project name has been specified.
# Try expanding the name, substituting the username. If the
# result is different then we use it, not if it is the same
# which would suggest it isn't unique.

project = os.environ.get('OPENSHIFT_PROJECT')

if project:
name = project.format(username=spawner.user.name)
if name != project:
pod.spec.containers[0].env.append(
dict(name='PROJECT_NAMESPACE', value=name))

# Ensure project is created if it doesn't exist.

pod.spec.containers[0].env.append(
dict(name='OPENSHIFT_PROJECT', value=name))

return pod

c.KubeSpawner.modify_pod_hook = modify_pod_hook

# Setup culling of terminal instances if timeout parameter is supplied.

idle_timeout = os.environ.get('JUPYTERHUB_IDLE_TIMEOUT')

if idle_timeout and int(idle_timeout):
cull_idle_servers_cmd = ['/opt/app-root/bin/cull-idle-servers']

cull_idle_servers_cmd.append('--timeout=%s' % idle_timeout)

c.JupyterHub.services.extend([
{
'name': 'cull-idle',
'admin': True,
'command': cull_idle_servers_cmd,
}
])
7 changes: 7 additions & 0 deletions jupyterhub_config-workspace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
KUBERNETES_SERVER_URL="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT"
OAUTH_METADATA_URL="$KUBERNETES_SERVER_URL/.well-known/oauth-authorization-server"
OAUTH_ISSUER_ADDRESS=`curl -ks $OAUTH_METADATA_URL | grep '"issuer":' | sed -e 's%.*https://%https://%' -e 's%",%%'`

export OPENSHIFT_URL=$OAUTH_ISSUER_ADDRESS
export OPENSHIFT_REST_API_URL=$KUBERNETES_SERVER_URL
export OPENSHIFT_AUTH_API_URL=$OAUTH_ISSUER_ADDRESS
26 changes: 26 additions & 0 deletions jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ def resolve_image_name(name):

# Define the default configuration for JupyterHub application.

c.Spawner.environment = dict()

c.JupyterHub.services = []

c.KubeSpawner.init_containers = []

c.KubeSpawner.extra_containers = []

c.JupyterHub.extra_handlers = []

c.JupyterHub.port = 8080

c.JupyterHub.hub_ip = '0.0.0.0'
Expand Down Expand Up @@ -191,6 +201,11 @@ def resolve_image_name(name):
if os.environ.get('JUPYTERHUB_NOTEBOOK_MEMORY'):
c.Spawner.mem_limit = convert_size_to_bytes(os.environ['JUPYTERHUB_NOTEBOOK_MEMORY'])

notebook_interface = os.environ.get('JUPYTERHUB_NOTEBOOK_INTERFACE')

if notebook_interface:
c.Spawner.environment['JUPYTER_NOTEBOOK_INTERFACE'] = notebook_interface

# Workaround bug in minishift where a service cannot be contacted from a
# pod which backs the service. For further details see the minishift issue
# https://github.com/minishift/minishift/issues/2400.
Expand Down Expand Up @@ -234,6 +249,17 @@ def _wrapper_get_env(wrapped, instance, args, kwargs):

return env

# Load configuration overrides based on configuration type.

configuration_type = os.environ.get('CONFIGURATION_TYPE')

if configuration_type:
config_file = '/opt/app-root/etc/jupyterhub_config-%s.py' % configuration_type

if os.path.exists(config_file):
with open(config_file) as fp:
exec(compile(fp.read(), config_file, 'exec'), globals())

# Load configuration included in the image.

image_config_file = '/opt/app-root/src/.jupyter/jupyterhub_config.py'
Expand Down
6 changes: 6 additions & 0 deletions jupyterhub_config.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
if [ x"$CONFIGURATION_TYPE" != x"" ]; then
if [ -f /opt/app-root/etc/jupyterhub_config-$CONFIGURATION_TYPE.sh ]; then
. /opt/app-root/etc/jupyterhub_config-$CONFIGURATION_TYPE.sh
fi
fi

if [ -f /opt/app-root/src/.jupyter/jupyterhub_config.sh ]; then
. /opt/app-root/src/.jupyter/jupyterhub_config.sh
fi
Expand Down
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
kubernetes==9.0.1
jupyterhub==0.9.6
jupyterhub==1.0.0
#jupyterhub-kubespawner==0.10.1
git+https://github.com/jupyter-on-openshift/kubespawner.git@055f6f29fd4d7ee56d1e9cca1aacdd6f5fd19507#egg=jupyterhub-kubespawner
git+https://github.com/jupyterhub/kubespawner.git@a945ef01410867b39e0c174d362a8702bbaa15e9#egg=jupyterhub-kubespawner
git+https://github.com/jupyterhub/wrapspawner.git@5f2b7075f77d0c1c49066682a8e8adad0dab76db
jupyterhub-tmpauthenticator==0.6
oauthenticator==0.9.0
jupyterhub-ldapauthenticator==1.2.2
psycopg2==2.8.3
openshift==0.9.2
psycopg2==2.8.4
openshift==0.10.0
wrapt==1.11.2
2 changes: 1 addition & 1 deletion templates/jupyterhub-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
{
"name": "BUILDER_IMAGE",
"value": "jupyterhub:3.3.1",
"value": "jupyterhub:3.4.0",
"required": true
},
{
Expand Down
2 changes: 1 addition & 1 deletion templates/jupyterhub-deployer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
{
"name": "JUPYTERHUB_IMAGE",
"value": "jupyterhub:3.3.1",
"value": "jupyterhub:3.4.0",
"required": true
},
{
Expand Down
10 changes: 9 additions & 1 deletion templates/jupyterhub-quickstart.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
{
"name": "JUPYTERHUB_IMAGE",
"value": "jupyterhub:3.3.1",
"value": "jupyterhub:3.4.0",
"required": true
},
{
Expand Down Expand Up @@ -75,6 +75,10 @@
"value": "512Mi",
"required": true
},
{
"name": "NOTEBOOK_INTERFACE",
"value": "classic"
},
{
"name": "NOTEBOOK_MEMORY",
"description": "Amount of memory available to each notebook.",
Expand Down Expand Up @@ -294,6 +298,10 @@
"name": "JUPYTERHUB_NOTEBOOK_MEMORY",
"value": "${NOTEBOOK_MEMORY}"
},
{
"name": "JUPYTERHUB_NOTEBOOK_INTERFACE",
"value": "${NOTEBOOK_INTERFACE}"
},
{
"name": "JUPYTERHUB_DATABASE_PASSWORD",
"value": "${DATABASE_PASSWORD}"
Expand Down
Loading

0 comments on commit a294175

Please sign in to comment.