forked from jupyterhub/dockerspawner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
systemuserspawner.py
157 lines (135 loc) · 5.11 KB
/
systemuserspawner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from dockerspawner import DockerSpawner
from textwrap import dedent
from traitlets import (
Integer,
Unicode,
)
class SystemUserSpawner(DockerSpawner):
host_homedir_format_string = Unicode(
"/home/{username}",
config=True,
help=dedent(
"""
Format string for the path to the user's home directory on the host.
The format string should include a `username` variable, which will
be formatted with the user's username.
If the string is empty or `None`, the user's home directory will
be looked up via the `pwd` database.
"""
)
)
image_homedir_format_string = Unicode(
"/home/{username}",
config=True,
help=dedent(
"""
Format string for the path to the user's home directory
inside the image. The format string should include a
`username` variable, which will be formatted with the
user's username.
"""
)
)
user_id = Integer(-1,
help=dedent(
"""
If system users are being used, then we need to know their user id
in order to mount the home directory.
User IDs are looked up in two ways:
1. stored in the state dict (authenticator can write here)
2. lookup via pwd
"""
)
)
@property
def host_homedir(self):
"""
Path to the volume containing the user's home directory on the host.
Looked up from `pwd` if an empty format string or `None` has been specified.
"""
if self.host_homedir_format_string is not None and self.host_homedir_format_string != "":
homedir = self.host_homedir_format_string.format(username=self.user.name)
else:
import pwd
homedir = pwd.getpwnam(self.user.name).pw_dir
return homedir
@property
def homedir(self):
"""
Path to the user's home directory in the docker image.
"""
return self.image_homedir_format_string.format(username=self.user.name)
@property
def volume_mount_points(self):
"""
Volumes are declared in docker-py in two stages. First, you declare
all the locations where you're going to mount volumes when you call
create_container.
Returns a list of all the values in self.volumes or
self.read_only_volumes.
"""
mount_points = super(SystemUserSpawner, self).volume_mount_points
mount_points.append(self.homedir)
return mount_points
@property
def volume_binds(self):
"""
The second half of declaring a volume with docker-py happens when you
actually call start(). The required format is a dict of dicts that
looks like:
{
host_location: {'bind': container_location, 'ro': True}
}
"""
volumes = super(SystemUserSpawner, self).volume_binds
volumes[self.host_homedir] = {
'bind': self.homedir,
'ro': False
}
return volumes
def get_env(self):
env = super(SystemUserSpawner, self).get_env()
# relies on NB_USER and NB_UID handling in jupyter/docker-stacks
env.update(dict(
USER=self.user.name, # deprecated
NB_USER=self.user.name,
USER_ID=self.user_id, # deprecated
NB_UID=self.user_id,
HOME=self.homedir,
))
return env
def _user_id_default(self):
"""
Get user_id from pwd lookup by name
If the authenticator stores user_id in the user state dict,
this will never be called, which is necessary if
the system users are not on the Hub system (i.e. Hub itself is in a container).
"""
import pwd
return pwd.getpwnam(self.user.name).pw_uid
def load_state(self, state):
super().load_state(state)
if 'user_id' in state:
self.user_id = state['user_id']
def get_state(self):
state = super().get_state()
if self.user_id >= 0:
state['user_id'] = self.user_id
return state
def start(self, *, image=None, extra_create_kwargs=None,
extra_host_config=None):
"""start the single-user server in a docker container"""
if image:
self.log.warning("Specifying image via .start args is deprecated")
self.image = image
if extra_create_kwargs:
self.log.warning("Specifying extra_create_kwargs via .start args is deprecated")
self.extra_create_kwargs.update(extra_create_kwargs)
if extra_host_config:
self.log.warning("Specifying extra_host_config via .start kwargs is deprecated")
self.extra_host_config.update(extra_host_config)
self.extra_create_kwargs.setdefault('working_dir', self.homedir)
# systemuser image must be started as root
# relies on NB_UID and NB_USER handling in docker-stacks
self.extra_create_kwargs.setdefault('user', '0')
return super(SystemUserSpawner, self).start()