Skip to content

Commit

Permalink
add image_whitelist and default options form
Browse files Browse the repository at this point in the history
if c.DockerSpawner.image_whitelist is specified, a default options form is constructed
with a select dropdown for choosing the image to launch.

image_whitelist is a dict of key: image, where the key is the value to be used in the form,
while the values are the actual docker images.

image_whitelist may be specified as a list, in which case it is cast to a dict of the form {image:image},
where the keys are the same as the values (users see actual image names, rather than abbreviations/descriptions).
  • Loading branch information
minrk committed Feb 10, 2018
1 parent fdeef7b commit f5787ad
Showing 1 changed file with 77 additions and 5 deletions.
82 changes: 77 additions & 5 deletions dockerspawner/dockerspawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@
import docker
from docker.errors import APIError
from docker.utils import kwargs_from_env
from tornado import gen
from tornado import gen, web

from escapism import escape
from jupyterhub.spawner import Spawner
from traitlets import (
Dict,
Unicode,
Any,
Bool,
Dict,
List,
Int,
Any,
Unicode,
Union,
default,
observe,
validate,
)

from .volumenamingstrategy import default_format_volume_name
Expand Down Expand Up @@ -142,6 +145,57 @@ def _container_image_changed(self, change):
"""
)

image_whitelist = Union([Dict(), List()],
config=True,
help="""
List or dict of images that users can run.
If specified, users will be presented with a form
from which they can select an image to run.
"""
)

@validate('image_whitelist')
def _image_whitelist_dict(self, proposal):
"""cast image_whitelist to a dict
If passing a list, cast it to a {item:item}
dict where the keys and values are the same.
"""
whitelist = proposal.value
if not isinstance(whitelist, dict):
whitelist = {item:item for item in whitelist}
return whitelist

@default('options_form')
def _default_options_form(self):
if len(self.image_whitelist) <= 1:
# default form only when there are images to choose from
return
# form derived from wrapspawner.ProfileSpawner
option_t = '<option value="{image}" {selected}>{image}</option>'
options = [
option_t.format(
image=image,
selected='selected' if image == self.image else ''
)
for image in self.image_whitelist
]
return """
<label for="image">Select an image:</label>
<select class="form-control" name="image" required autofocus>
{options}
</select>
""".format(options=options)

def options_from_form(self, formdata):
"""Turn options formdata into user_options"""
options = {}
print(formdata)
if 'image' in formdata:
options['image'] = formdata['image'][0]
return options

container_prefix = Unicode(
"jupyter",
config=True,
Expand Down Expand Up @@ -476,6 +530,25 @@ def start(self, image=None, extra_create_kwargs=None,
`extra_host_config` take precedence over their global counterparts.
"""
# image priority:
# 1. explicit argument
# (this never happens when DockerSpawner is used directly,
# but can be used by subclasses)
# 2. user options (from spawn options form)
# 3. self.image from config
image = image or self.user_options.get('image') or self.image
if self.image_whitelist:
if image not in self.image_whitelist:
raise web.HTTPError(400,
"Image %s not in whitelist: %s" % (
image, ', '.join(self.image_whitelist)
)
)
# resolve image alias to actual image name
image = self.image_whitelist[image]
# save choice in self.image
self.image = image

container = yield self.get_container()
if container and self.remove_containers:
self.log.warning(
Expand All @@ -486,7 +559,6 @@ def start(self, image=None, extra_create_kwargs=None,
container = None

if container is None:
image = image or self.image
if self._user_set_cmd:
cmd = self.cmd
else:
Expand Down

0 comments on commit f5787ad

Please sign in to comment.