Permalink
Please sign in to comment.
Browse files
Merge pull request #205 from StrausMG/byor
Bring Your Own Resources
- Loading branch information...
Showing
with
192 additions
and 7 deletions.
- +1 −0 .gitignore
- +5 −0 etc/byor_config.py
- +2 −1 everware/__init__.py
- +88 −0 everware/byor_spawner.py
- +11 −6 everware/spawner.py
- +85 −0 share/static/html/_byor_options_form.html
| @@ -0,0 +1,5 @@ | |||
| +c = get_config() | |||
| +load_subconfig('etc/base_config.py') | |||
| +load_subconfig('etc/github_auth.py') | |||
| + | |||
| +c.JupyterHub.spawner_class = 'everware.ByorDockerSpawner' | |||
| @@ -0,0 +1,88 @@ | |||
| +from os.path import join as pjoin | |||
| + | |||
| +import docker | |||
| +from docker.errors import DockerException | |||
| +from traitlets import Int | |||
| +from tornado import gen | |||
| + | |||
| +from .spawner import CustomDockerSpawner | |||
| + | |||
| + | |||
| +class ByorDockerSpawner(CustomDockerSpawner): | |||
| + def __init__(self, **kwargs): | |||
| + CustomDockerSpawner.__init__(self, **kwargs) | |||
| + self._byor_client = None | |||
| + if self.options_form == self._options_form_default(): | |||
| + with open(pjoin(self.config['JupyterHub']['template_paths'][0], | |||
| + '_byor_options_form.html')) as form: | |||
| + ByorDockerSpawner.options_form = form.read() | |||
| + | |||
| + @property | |||
| + def client(self): | |||
| + if self._byor_client is not None: | |||
| + return self._byor_client | |||
| + return super(ByorDockerSpawner, self).client | |||
| + | |||
| + @property | |||
| + def byor_is_used(self): | |||
| + return self.user_options.get('byor_is_needed', False) | |||
| + | |||
| + def _reset_byor(self): | |||
| + self.container_ip = str(self.__class__.container_ip) | |||
| + self._byor_client = None | |||
| + | |||
| + byor_timeout = Int(20, min=1, config=True, | |||
| + help='Timeout for connection to BYOR Docker daemon') | |||
| + | |||
| + def options_from_form(self, formdata): | |||
| + options = {} | |||
| + options['byor_is_needed'] = formdata.pop('byor_is_needed', [''])[0].strip() == 'on' | |||
| + for field in ('byor_docker_ip', 'byor_docker_port'): | |||
| + options[field] = formdata.pop(field, [''])[0].strip() | |||
| + options.update( | |||
| + super(ByorDockerSpawner, self).options_from_form(formdata) | |||
| + ) | |||
| + return options | |||
| + | |||
| + @gen.coroutine | |||
| + def _configure_byor(self): | |||
| + """Configure BYOR settings or reset them if BYOR is not needed.""" | |||
| + if not self.byor_is_used: | |||
| + self._reset_byor() | |||
| + return | |||
| + byor_ip = self.user_options['byor_docker_ip'] | |||
| + byor_port = self.user_options['byor_docker_port'] | |||
| + try: | |||
| + # version='auto' causes a connection to the daemon. | |||
| + # That's why the method must be a coroutine. | |||
| + self._byor_client = docker.Client('{}:{}'.format(byor_ip, byor_port), | |||
| + version='auto', | |||
| + timeout=self.byor_timeout) | |||
| + except DockerException as e: | |||
| + self._is_failed = True | |||
| + message = str(e) | |||
| + if 'ConnectTimeoutError' in message: | |||
| + log_message = 'Connection to the Docker daemon took too long (> {} secs)'.format( | |||
| + self.byor_timeout | |||
| + ) | |||
| + notification_message = 'BYOR timeout limit {} exceeded'.format(self.byor_timeout) | |||
| + else: | |||
| + log_message = "Failed to establish connection with the Docker daemon" | |||
| + notification_message = log_message | |||
| + self._add_to_log(log_message, level=2) | |||
| + yield self.notify_about_fail(notification_message) | |||
| + self._is_building = False | |||
| + raise | |||
| + | |||
| + self.container_ip = byor_ip | |||
| + | |||
| + @gen.coroutine | |||
| + def _prepare_for_start(self): | |||
| + super(ByorDockerSpawner, self)._prepare_for_start() | |||
| + yield self._configure_byor() | |||
| + | |||
| + @gen.coroutine | |||
| + def start(self, image=None): | |||
| + yield self._prepare_for_start() | |||
| + ip_port = yield self._start(image) | |||
| + return ip_port | |||
| @@ -0,0 +1,85 @@ | |||
| +<div style="margin-bottom: 0px;"> | |||
| +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label" style="width: 50%;"> | |||
| +<input | |||
| + id="repository_input" | |||
| + type="text" | |||
| + autocapitalize="off" | |||
| + autocorrect="off" | |||
| + name="repository_url" | |||
| + tabindex="1" | |||
| + autofocus="autofocus" | |||
| + class="mdl-textfield__input" | |||
| +style="margin-bottom: 3px;" /> | |||
| +<label class="mdl-textfield__label" for="repository_input">Git repository</label> | |||
| +</div> | |||
| + | |||
| +<label for="need_remove" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" > | |||
| +<input type="checkbox" | |||
| + name="need_remove" | |||
| + class="mdl-checkbox__input" | |||
| + id="need_remove" | |||
| + checked /> | |||
| +<span class="mdl-checkbox__label">Remove previous container if it exists</span> | |||
| +</label> | |||
| + | |||
| +<label for="byor_is_needed" class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" > | |||
| +<input type="checkbox" | |||
| + name="byor_is_needed" | |||
| + class="mdl-checkbox__input" | |||
| + id="byor_is_needed" | |||
| + unchecked /> | |||
| +<span class="mdl-checkbox__label">I want to run the repository on my own server</span> | |||
| +</label> | |||
| +<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> | |||
| +<script> | |||
| +window.onload = function () { | |||
| + byor_is_needed = document.getElementById("byor_is_needed"); | |||
| + byor_input = document.getElementById("byor_input"); | |||
| + if (byor_is_needed.checked) { | |||
| + byor_input.style.display = "inline"; | |||
| + } else { | |||
| + byor_input.style.display = "none"; | |||
| + } | |||
| +} | |||
| +$('#byor_is_needed').on('change', function() { | |||
| + $('#byor_input').toggle(speed='normal'); | |||
| +}); | |||
| +</script> | |||
| + | |||
| +<div id='byor_input' style="display: none;"> | |||
| +<p style="margin-bottom: 0px;"> | |||
| + For a successful run <a href="https://www.docker.com/" target="_black">Docker</a> | |||
| + must be installed on your server.<br /> | |||
| + Enter IP address and port of the Docker daemon running on your server.<br /> | |||
| + (Click <a href="https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option" | |||
| + target="_blank"> | |||
| + here</a> to learn how to run Docker daemon on a particular port) | |||
| +</p> | |||
| +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label" | |||
| + style="width: 9%; margin-right: 20px;"> | |||
| + <input | |||
| + id="byor_docker_ip" | |||
| + type="text" | |||
| + autocapitalize="off" | |||
| + autocorrect="off" | |||
| + name="byor_docker_ip" | |||
| + tabindex="1" | |||
| + autofocus="autofocus" | |||
| + class="mdl-textfield__input"/> | |||
| + <label class="mdl-textfield__label" for="byor_docker_ip">ip</label> | |||
| +</div> | |||
| +<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label" | |||
| + style="width: 9%;"> | |||
| + <input | |||
| + id="byor_docker_port" | |||
| + type="text" | |||
| + autocapitalize="off" | |||
| + autocorrect="off" | |||
| + name="byor_docker_port" | |||
| + tabindex="1" | |||
| + autofocus="autofocus" | |||
| + class="mdl-textfield__input"/> | |||
| + <label class="mdl-textfield__label" for="byor_docker_port">port</label> | |||
| +</div> | |||
| +</div> | |||
| +</div> | |||
0 comments on commit
7e9dead