Skip to content

Commit

Permalink
Merge pull request #137 from gsemet/profile_spawner
Browse files Browse the repository at this point in the history
Add kube profile spawner
  • Loading branch information
yuvipanda committed Apr 17, 2018
2 parents 085cb30 + efb48ae commit 5170da9
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 6 deletions.
145 changes: 140 additions & 5 deletions kubespawner/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from kubernetes.client.rest import ApiException
from kubernetes import client
import escapism
from jinja2 import Environment, BaseLoader

from .clients import shared_client
from kubespawner.traitlets import Callable
Expand Down Expand Up @@ -630,7 +631,7 @@ def _hub_connect_port_default(self):
List of access modes the user has for the pvc.
The access modes are:
- `ReadWriteOnce` – the volume can be mounted as read-write by a single node
- `ReadOnlyMany` – the volume can be mounted read-only by many nodes
- `ReadWriteMany` – the volume can be mounted as read-write by many nodes
Expand All @@ -649,7 +650,7 @@ def _hub_connect_port_default(self):
The keys is name of hooks and there are only two hooks, postStart and preStop.
The values are handler of hook which executes by Kubernetes management system when hook is called.
Below is an sample copied from
Below is an sample copied from
`Kubernetes doc <https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/>`_ ::
lifecycle:
Expand Down Expand Up @@ -688,7 +689,7 @@ def _hub_connect_port_default(self):
add:
- NET_ADMIN
See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ for more
info on what init containers are and why you might want to use them!
Expand Down Expand Up @@ -754,15 +755,15 @@ def _hub_connect_port_default(self):
which follows spec at https://v1-6.docs.kubernetes.io/docs/api-reference/v1.6/#container-v1-core.
One usage is setting crontab in a container to clean sensitive data with configuration below::
[
{
'name': 'crontab',
'image': 'supercronic',
'command': ['/usr/local/bin/supercronic', '/etc/crontab']
}
]
"""
)

Expand Down Expand Up @@ -800,6 +801,88 @@ def _hub_connect_port_default(self):
"""
)

profile_form_template = Unicode(
"""
<label for="profile">Please select a profile to launch</label>
<select class="form-control" name="profile" required autofocus>
{% for profile in profile_list %}
<option {% if profile.default %}selected{% endif %} value="{{ loop.index0 }}">{{ profile.display_name }}</option>
{% endfor %}
</select>
""",
config=True,
help="""
Jinja2 template for constructing profile list shown to user.
Used when `profile_list` is set.
The contents of `profile_list` are passed in to the template.
This should be used to construct the contents of a HTML form. When
posted, this form is expected to have an item with name `profile` and
the value the index of the profile in `profile_list`.
"""
)

profile_list = List(
trait=Dict(),
default_value=None,
minlen=0,
config=True,
help="""
List of profiles to offer for selection by the user.
Signature is: List(Dict()), where each item is a dictionary that has two keys:
- 'display_name': the human readable display name (should be HTML safe)
- 'kubespawner_override': a dictionary with overrides to apply to the KubeSpawner
settings. Each value can be either the final value to change or a callable that
take the `KubeSpawner` instance as parameter and return the final value.
- 'default': (optional Bool) True if this is the default selected option
Example::
c.KubeSpawner.profile_list = [
{
'display_name': 'Training Env - Python',
'default': True,
'kubespawner_override': {
'singleuser_image_spec': 'training/python:label',
'cpu_limit': 1,
'mem_limit': '512M',
}
}, {
'display_name': 'Training Env - Datascience',
'kubespawner_override': {
'singleuser_image_spec': 'training/datascience:label',
'cpu_limit': 4,
'mem_limit': '8G',
}
}, {
'display_name': 'DataScience - Small instance',
'kubespawner_override': {
'singleuser_image_spec': 'datascience/small:label',
'cpu_limit': 10,
'mem_limit': '16G',
}
}, {
'display_name': 'DataScience - Medium instance',
'kubespawner_override': {
'singleuser_image_spec': 'datascience/medium:label',
'cpu_limit': 48,
'mem_limit': '96G',
}
}, {
'display_name': 'DataScience - Medium instance (GPUx2)',
'kubespawner_override': {
'singleuser_image_spec': 'datascience/medium:label',
'cpu_limit': 48,
'mem_limit': '96G',
'extra_resource_guarantees': {"nvidia.com/gpu": "2"},
}
}
]
"""
)

def _expand_user_properties(self, template):
# Make sure username and servername match the restrictions for DNS labels
safe_chars = set(string.ascii_lowercase + string.digits)
Expand Down Expand Up @@ -1127,3 +1210,55 @@ def get_args(self):
args[i] = '--hub-api-url="%s"' % (self.accessible_hub_api_url)
break
return args

def _options_form_default(self):
'''
Build the form template according to the `profile_list` setting.
Returns:
'' when no `profile_list` has been defined
The rendered template (using jinja2) when `profile_list` is defined.
'''
if not self.profile_list:
return ''
profile_form_template = Environment(loader=BaseLoader).from_string(self.profile_form_template)
return profile_form_template.render(profile_list=self.profile_list)

def options_from_form(self, formdata):
"""get the option selected by the user on the form
It actually reset the settings of kubespawner to each item found in the selected profile
(`kubespawner_override`).
Args:
formdata: user selection returned by the form
To access to the value, you can use the `get` accessor and the name of the html element,
for example::
formdata.get('profile',[0])
to get the value of the form named "profile", as defined in `form_template`::
<select class="form-control" name="profile"...>
</select>
Returns:
the selected user option
"""

if not self.profile_list:
return formdata
# Default to first profile if somehow none is provided
selected_profile = int(formdata.get('profile', [0])[0])
options = self.profile_list[selected_profile]
self.log.debug("Applying KubeSpawner override for profile '%s'", options['display_name'])
kubespawner_override = options.get('kubespawner_override', {})
for k, v in kubespawner_override.items():
if callable(v):
v = v(self)
self.log.debug(".. overriding KubeSpawner value %s=%s (callable result)", k, v)
else:
self.log.debug(".. overriding KubeSpawner value %s=%s", k, v)
setattr(self, k, v)
return options
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'pyYAML',
'kubernetes==4.*',
'escapism',
'jinja2',
],
setup_requires=['pytest-runner'],
tests_require=['pytest'],
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
minversion = 1.6
skipsdist = True

envlist = py34
envlist = py34,py35,py36

[flake8]
max-line-length = 100

0 comments on commit 5170da9

Please sign in to comment.