add basic CLI and config file support

See `jupyterhub -h` for common shortcuts

default config file: `jupyter_hub_config.py`
generate config file with: `jupyterhub --generate-config`
non-default config file: `jupyterhub -f myconfig.py`
This commit is contained in:
MinRK
2014-09-16 14:47:48 -07:00
parent e7f67bfc4f
commit 83e9762845
4 changed files with 139 additions and 11 deletions

View File

@@ -66,4 +66,13 @@ and then visit `http://localhost:8000`, and sign in with your unix credentials.
If you want multiple users to be able to sign into the server, you will need to run the
`jupyterhub` command as a privileged user, such as root.
### Some examples
generate a default config file:
jupyterhub --generate-config
spawn the server on 10.0.1.2:443 with https:
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert

View File

@@ -4,11 +4,17 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import io
import logging
import os
from subprocess import Popen
try:
raw_input
except NameError:
# py3
raw_input = input
from jinja2 import Environment, FileSystemLoader
import tornado.httpserver
@@ -32,15 +38,77 @@ from . import orm
from ._data import DATA_FILES_PATH
from .utils import url_path_join
# classes for config
from .auth import Authenticator, PAMAuthenticator
from .spawner import Spawner, LocalProcessSpawner
aliases = {
'log-level': 'Application.log_level',
'f': 'JupyterHubApp.config_file',
'config': 'JupyterHubApp.config_file',
'y': 'JupyterHubApp.answer_yes',
'ssl-key': 'JupyterHubApp.ssl_key',
'ssl-cert': 'JupyterHubApp.ssl_cert',
'ip': 'JupyterHubApp.ip',
'port': 'JupyterHubApp.port',
'db': 'JupyterHubApp.db_url',
'pid-file': 'JupyterHubApp.pid_file',
}
flags = {
'debug': ({'Application' : {'log_level' : logging.DEBUG}},
"set log level to logging.DEBUG (maximize logging output)"),
'generate-config': ({'JupyterHubApp': {'generate_config' : True}},
"generate default config file")
}
class JupyterHubApp(Application):
"""An Application for starting a Multi-User Jupyter Notebook server."""
description = """Start a multi-user Jupyter Notebook server
spawns a configurable-http-proxy and multi-user Hub,
Spawns a configurable-http-proxy and multi-user Hub,
which authenticates users and spawns single-user Notebook servers
on behalf of users.
"""
examples = """
generate default config file:
jupyterhub --generate-config -f myconfig.py
spawn the server on 10.0.1.2:443 with https:
jupyterhub --ip 10.0.1.2 --port 443 --ssl-key my_ssl.key --ssl-cert my_ssl.cert
"""
aliases = Dict(aliases)
flags = Dict(flags)
classes = List([
Spawner,
LocalProcessSpawner,
Authenticator,
PAMAuthenticator,
])
config_file = Unicode('jupyter_hub_config.py', config=True,
help="The config file to load",
)
generate_config = Bool(False, config=True,
help="Generate default config file",
)
answer_yes = Bool(False, config=True,
help="Answer yes to any questions (e.g. confirm overwrite)"
)
pid_file = Unicode('', config=True,
help="""File to write PID
Useful for daemonizing jupyterhub.
"""
)
data_files_path = Unicode(DATA_FILES_PATH, config=True,
help="The location of jupyter data files (e.g. /usr/local/share/jupyter)"
)
@@ -279,7 +347,7 @@ class JupyterHubApp(Application):
cmd.extend(['--ssl-key', self.ssl_key])
if self.ssl_cert:
cmd.extend(['--ssl-cert', self.ssl_cert])
self.log.info("Starting proxy: %s", cmd)
self.proxy_process = Popen(cmd, env=env)
def init_tornado_settings(self):
@@ -315,9 +383,20 @@ class JupyterHubApp(Application):
"""Instantiate the tornado Application object"""
self.tornado_application = web.Application(self.handlers, **self.tornado_settings)
def write_pid_file(self):
pid = os.getpid()
if self.pid_file:
self.log.debug("Writing PID %i to %s", pid, self.pid_file)
with io.open(self.pid_file, 'w') as f:
f.write(u'%i' % pid)
@catch_config_error
def initialize(self, *args, **kwargs):
super(JupyterHubApp, self).initialize(*args, **kwargs)
if self.generate_config:
return
self.load_config_file(self.config_file)
self.write_pid_file()
self.init_logging()
self.init_ports()
self.init_db()
@@ -329,25 +408,56 @@ class JupyterHubApp(Application):
@gen.coroutine
def cleanup(self):
self.log.info("Cleaning up proxy...")
self.proxy_process.terminate()
"""Shutdown our various subprocesses and cleanup runtime files."""
self.log.info("Cleaning up single-user servers...")
# request (async) process termination
futures = []
for user in self.db.query(orm.User):
if user.spawner is not None:
futures.append(user.spawner.stop())
# clean up proxy while SUS are shutting down
self.log.info("Cleaning up proxy[%i]..." % self.proxy_process.pid)
self.proxy_process.terminate()
# wait for the requests to stop finish:
for f in futures:
yield f
if self.pid_file and os.path.exists(self.pid_file):
self.log.info("Cleaning up PID file %s", self.pid_file)
os.remove(self.pid_file)
# finally stop the loop once we are all cleaned up
self.log.info("...done")
def write_config_file(self):
if os.path.exists(self.config_file) and not self.answer_yes:
answer = ''
def ask():
prompt = "Overwrite %s with default config? [y/N]" % self.config_file
try:
return raw_input(prompt).lower() or 'n'
except KeyboardInterrupt:
print('') # empty line
return 'n'
answer = ask()
while not answer.startswith(('y', 'n')):
print("Please answer 'yes' or 'no'")
answer = ask()
if answer.startswith('n'):
return
config_text = self.generate_config_file()
print("Writing default config to: %s" % self.config_file)
with io.open(self.config_file, encoding='utf8', mode='w') as f:
f.write(config_text)
def start(self):
"""Start the whole thing"""
if self.generate_config:
self.write_config_file()
return
# start the proxy
self.start_proxy()
# start the webserver

View File

@@ -49,11 +49,9 @@ class Spawner(LoggingConfigurable):
self._env_key(env, 'API_TOKEN', self.api_token)
return env
cmd = List(Unicode, config=True,
cmd = List(Unicode, default_value=['jupyterhub-singleuser'], config=True,
help="""The command used for starting notebooks."""
)
def _cmd_default(self):
return ['jupyterhub-singleuser']
@classmethod
def fromJSON(cls, state, **kwargs):
@@ -183,8 +181,8 @@ class LocalProcessSpawner(Spawner):
set_user = Enum(['sudo', 'setuid'], default_value='setuid', config=True,
help="""scheme for setting the user of the spawned process
sudo can be more prudently restricted,
but setuid is simpler for a server run as root
'sudo' can be more prudently restricted,
but 'setuid' is simpler for a server run as root
"""
)
def _set_user_changed(self, name, old, new):

View File

@@ -0,0 +1,11 @@
"""Test the JupyterHubApp entry point"""
import sys
from subprocess import check_output
def test_help_all():
out = check_output([sys.executable, '-m', 'jupyterhub', '--help-all']).decode('utf8', 'replace')
assert u'--ip' in out
assert u'--JupyterHubApp.ip' in out