mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 02:24:08 +00:00
jupyterhub-1219 Enhancement: automatically create a directory for the user after successful login
This commit is contained in:
130
examples/bootstrap-script/README.md
Normal file
130
examples/bootstrap-script/README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Bootstrapping your users
|
||||||
|
|
||||||
|
Before spawning a notebook to the user, it could be useful to
|
||||||
|
do some preparation work in a bootstrapping process.
|
||||||
|
|
||||||
|
Common use cases are:
|
||||||
|
|
||||||
|
*Providing writeable storage for LDAP users*
|
||||||
|
|
||||||
|
Your Jupyterhub is configured to use the LDAPAuthenticator and DockerSpawer.
|
||||||
|
|
||||||
|
* The user has no file directory on the host since your are using LDAP.
|
||||||
|
* When a user has no directory and DockerSpawner wants to mount a volume,
|
||||||
|
the spawner will use docker to create a directory.
|
||||||
|
Since the docker daemon is running as root, the generated directory for the volume
|
||||||
|
mount will not be writeable by the `jovyan` user inside of the container.
|
||||||
|
For the directory to be useful to the user, the permissions on the directory
|
||||||
|
need to be modified for the user to have write access.
|
||||||
|
|
||||||
|
*Prepopulating Content*
|
||||||
|
|
||||||
|
Another use would be to copy initial content, such as tutorial files or reference
|
||||||
|
material, into the user's space when a notebook server is newly spawned.
|
||||||
|
|
||||||
|
You can define your own bootstrap process by implementing a `pre_spawn_hook` on any spawner.
|
||||||
|
The Spawner itself is passed as parameter to your hook and you can easily get the contextual information out of the spawning process.
|
||||||
|
|
||||||
|
If you implement a hook, make sure that it is *idempotent*. It will be executed every time
|
||||||
|
a notebook server is spawned to the user. That means you should somehow
|
||||||
|
ensure that things which should run only once are not running again and again.
|
||||||
|
For example, before you create a directory, check if it exists.
|
||||||
|
|
||||||
|
Bootstrapping examples:
|
||||||
|
|
||||||
|
### Example #1 - Create a user directory
|
||||||
|
|
||||||
|
Create a directory for the user, if none exists
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# in jupyterhub_config.py
|
||||||
|
import os
|
||||||
|
def create_dir_hook(spawner):
|
||||||
|
username = spawner.user.name # get the username
|
||||||
|
volume_path = os.path.join('/volumes/jupyterhub', username)
|
||||||
|
if not os.path.exists(volume_path):
|
||||||
|
# create a directory with umask 0755
|
||||||
|
# hub and container user must have the same UID to be writeable
|
||||||
|
# still readable by other users on the system
|
||||||
|
os.mkdir(volume_path, 0o755)
|
||||||
|
# now do whatever you think your user needs
|
||||||
|
# ...
|
||||||
|
pass
|
||||||
|
|
||||||
|
# attach the hook function to the spawner
|
||||||
|
c.Spawner.pre_spawn_hook = create_dir_hook
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example #2 - Run a shell script
|
||||||
|
|
||||||
|
You can specify a plain ole' shell script (or any other executable) to be run
|
||||||
|
by the bootstrap process.
|
||||||
|
|
||||||
|
For example, you can execute a shell script and as first parameter pass the name
|
||||||
|
of the user:
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# in jupyterhub_config.py
|
||||||
|
from subprocess import check_call
|
||||||
|
import os
|
||||||
|
def my_script_hook(spawner):
|
||||||
|
username = spawner.user.name # get the username
|
||||||
|
script = os.path.join(os.path.dirname(__file__), 'bootstrap.sh')
|
||||||
|
check_call([script, username])
|
||||||
|
|
||||||
|
# attach the hook function to the spawner
|
||||||
|
c.Spawner.pre_spawn_hook = my_script_hook
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's an example on what you could do in your shell script. See also
|
||||||
|
`/examples/bootstrap-script/`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Bootstrap example script
|
||||||
|
# Copyright (c) Jupyter Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
# - The first parameter for the Bootstrap Script is the USER.
|
||||||
|
USER=$1
|
||||||
|
if ["$USER" == ""]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# This example script will do the following:
|
||||||
|
# - create one directory for the user $USER in a BASE_DIRECTORY (see below)
|
||||||
|
# - create a "tutorials" directory within and download and unzip
|
||||||
|
# the PythonDataScienceHandbook from GitHub
|
||||||
|
|
||||||
|
# Start the Bootstrap Process
|
||||||
|
echo "bootstrap process running for user $USER ..."
|
||||||
|
|
||||||
|
# Base Directory: All Directories for the user will be below this point
|
||||||
|
BASE_DIRECTORY=/volumes/jupyterhub/
|
||||||
|
|
||||||
|
# User Directory: That's the private directory for the user to be created, if none exists
|
||||||
|
USER_DIRECTORY=$BASE_DIRECTORY/$USER
|
||||||
|
|
||||||
|
if [ -d "$USER_DIRECTORY" ]; then
|
||||||
|
echo "...directory for user already exists. skipped"
|
||||||
|
exit 0 # all good. nothing to do.
|
||||||
|
else
|
||||||
|
echo "...creating a directory for the user: $USER_DIRECTORY"
|
||||||
|
mkdir $USER_DIRECTORY
|
||||||
|
|
||||||
|
echo "...initial content loading for user ..."
|
||||||
|
mkdir $USER_DIRECTORY/tutorials
|
||||||
|
cd $USER_DIRECTORY/tutorials
|
||||||
|
wget https://github.com/jakevdp/PythonDataScienceHandbook/archive/master.zip
|
||||||
|
unzip -o master.zip
|
||||||
|
rm master.zip
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
```
|
48
examples/bootstrap-script/bootstrap.sh
Executable file
48
examples/bootstrap-script/bootstrap.sh
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Bootstrap example script
|
||||||
|
# Copyright (c) Jupyter Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
# - The first parameter for the Bootstrap Script is the USER.
|
||||||
|
USER=$1
|
||||||
|
if ["$USER" == ""]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# This example script will do the following:
|
||||||
|
# - create one directory for the user $USER in a BASE_DIRECTORY (see below)
|
||||||
|
# - create a "tutorials" directory within and download and unzip the PythonDataScienceHandbook from GitHub
|
||||||
|
|
||||||
|
# Start the Bootstrap Process
|
||||||
|
echo "bootstrap process running for user $USER ..."
|
||||||
|
|
||||||
|
# Base Directory: All Directories for the user will be below this point
|
||||||
|
BASE_DIRECTORY=/volumes/jupyterhub
|
||||||
|
|
||||||
|
# User Directory: That's the private directory for the user to be created, if none exists
|
||||||
|
USER_DIRECTORY=$BASE_DIRECTORY/$USER
|
||||||
|
|
||||||
|
if [ -d "$USER_DIRECTORY" ]; then
|
||||||
|
echo "...directory for user already exists. skipped"
|
||||||
|
exit 0 # all good. nothing to do.
|
||||||
|
else
|
||||||
|
echo "...creating a directory for the user: $USER_DIRECTORY"
|
||||||
|
mkdir $USER_DIRECTORY
|
||||||
|
|
||||||
|
# mkdir did not succeed?
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "...initial content loading for user ..."
|
||||||
|
mkdir $USER_DIRECTORY/tutorials
|
||||||
|
cd $USER_DIRECTORY/tutorials
|
||||||
|
wget https://github.com/jakevdp/PythonDataScienceHandbook/archive/master.zip
|
||||||
|
unzip -o master.zip
|
||||||
|
rm master.zip
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
26
examples/bootstrap-script/jupyterhub_config.py
Normal file
26
examples/bootstrap-script/jupyterhub_config.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Example for a Spawner.pre_spawn_hook
|
||||||
|
# create a directory for the user before the spawner starts
|
||||||
|
|
||||||
|
import os
|
||||||
|
def create_dir_hook(spawner):
|
||||||
|
username = spawner.user.name # get the username
|
||||||
|
volume_path = os.path.join('/volumes/jupyterhub', username)
|
||||||
|
if not os.path.exists(volume_path):
|
||||||
|
os.mkdir(volume_path, 0o755)
|
||||||
|
# now do whatever you think your user needs
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# attach the hook function to the spawner
|
||||||
|
c.Spawner.pre_spawn_hook = create_dir_hook
|
||||||
|
|
||||||
|
# Use the DockerSpawner to serve your users' notebooks
|
||||||
|
c.JupyterHub.spawner_class = 'dockerspawner.DockerSpawner'
|
||||||
|
from jupyter_client.localinterfaces import public_ips
|
||||||
|
c.JupyterHub.hub_ip = public_ips()[0]
|
||||||
|
c.DockerSpawner.hub_ip_connect = public_ips()[0]
|
||||||
|
c.DockerSpawner.container_ip = "0.0.0.0"
|
||||||
|
|
||||||
|
# You can now mount the volume to the docker container as we've
|
||||||
|
# made sure the directory exists
|
||||||
|
c.DockerSpawner.volumes = { '/volumes/jupyterhub/{username}/': '/home/jovyan/work' }
|
||||||
|
|
@@ -349,6 +349,27 @@ class Spawner(LoggingConfigurable):
|
|||||||
"""
|
"""
|
||||||
).tag(config=True)
|
).tag(config=True)
|
||||||
|
|
||||||
|
pre_spawn_hook = Any(
|
||||||
|
None,
|
||||||
|
allow_none=True,
|
||||||
|
help="""
|
||||||
|
An optional hook function that you can implement to do some bootstrapping work before
|
||||||
|
the spawner starts. For example, create a directory for your user or load initial content.
|
||||||
|
|
||||||
|
This can be set independent of any concrete spawner implementation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
from subprocess import check_call
|
||||||
|
def my_hook(spawner):
|
||||||
|
username = spawner.user.name
|
||||||
|
check_call(['./examples/bootstrap-script/bootstrap.sh', username])
|
||||||
|
|
||||||
|
c.Spawner.pre_spawn_hook = my_hook
|
||||||
|
|
||||||
|
"""
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Spawner, self).__init__(**kwargs)
|
super(Spawner, self).__init__(**kwargs)
|
||||||
if self.user.state:
|
if self.user.state:
|
||||||
@@ -522,6 +543,19 @@ class Spawner(LoggingConfigurable):
|
|||||||
args.extend(self.args)
|
args.extend(self.args)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
def run_pre_spawn_hook(self):
|
||||||
|
"""
|
||||||
|
run a pre spawn hook function which you can define in your jupyterhub_config.py
|
||||||
|
:return: void
|
||||||
|
"""
|
||||||
|
if (callable(self.pre_spawn_hook)):
|
||||||
|
try:
|
||||||
|
self.pre_spawn_hook(self)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception("pre_spawn_hook failed with exception: %s", e)
|
||||||
|
raise e
|
||||||
|
return
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the single-user server
|
"""Start the single-user server
|
||||||
|
@@ -141,7 +141,6 @@ class User(HasTraits):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# pass get/setattr to ORM user
|
# pass get/setattr to ORM user
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
if hasattr(self.orm_user, attr):
|
if hasattr(self.orm_user, attr):
|
||||||
return getattr(self.orm_user, attr)
|
return getattr(self.orm_user, attr)
|
||||||
@@ -165,7 +164,7 @@ class User(HasTraits):
|
|||||||
if self.server is None:
|
if self.server is None:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def server(self):
|
def server(self):
|
||||||
if len(self.servers) == 0:
|
if len(self.servers) == 0:
|
||||||
@@ -283,6 +282,9 @@ class User(HasTraits):
|
|||||||
if (authenticator):
|
if (authenticator):
|
||||||
yield gen.maybe_future(authenticator.pre_spawn_start(self, spawner))
|
yield gen.maybe_future(authenticator.pre_spawn_start(self, spawner))
|
||||||
|
|
||||||
|
# run optional preparation work to bootstrap the notebook
|
||||||
|
yield gen.maybe_future(self.spawner.run_pre_spawn_hook())
|
||||||
|
|
||||||
self.spawn_pending = True
|
self.spawn_pending = True
|
||||||
# wait for spawner.start to return
|
# wait for spawner.start to return
|
||||||
try:
|
try:
|
||||||
|
Reference in New Issue
Block a user