-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
start jupyterhub-singleuser within docker spawer #95
Changes from all commits
6f0b950
3709e87
244bb3d
e5129fc
16a4ffc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import fcntl | ||
import grp | ||
import logging | ||
import os | ||
import pwd | ||
import subprocess | ||
import tempfile | ||
from pathlib import Path | ||
|
||
|
||
class SingleUserEnvManager: | ||
""" | ||
Manages the environment and user-specific settings for a single user in a JupyterHub server. | ||
""" | ||
|
||
def __init__(self, | ||
username, | ||
groupname: str = 'jupyterhub', ): | ||
""" | ||
Initializes the environment manager for the specified user. | ||
:param username: The name of the user for whom the environment is set up. | ||
:param groupname: The name of the OS system group to which the user belongs. | ||
""" | ||
self.username = username # OS system username | ||
self.groupname = groupname # OS system group | ||
|
||
# Inherit the current environment variables | ||
self.environment = dict(os.environ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still makes me nervous... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will get a whitelist later once system is up and running. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
self.global_home = Path(os.environ['JUPYTERHUB_USER_HOME']) | ||
self.user_dir = self.global_home / username | ||
self.log = self._init_logger() | ||
|
||
def setup_environment(self): | ||
""" | ||
Set up the user's environment by ensuring the system user exists, and the workspace | ||
permissions are correctly configured. | ||
""" | ||
self.log.info(f"Setting up environment for user: {self.username}") | ||
self._ensure_system_user() | ||
self._ensure_workspace_permission() | ||
|
||
def start_single_user_server(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. copied from customer_spawner.py |
||
""" | ||
Starts the single-user Jupyter server for the user, inheriting the configured environment. | ||
""" | ||
self.log.info(f'Starting single-user server for {self.username} with environment: {self.environment}') | ||
env_vars = [f'{key}={value}' for key, value in self.environment.items()] | ||
|
||
cmd = ['sudo', '-E', '-u', self.username, 'env'] + env_vars + [ | ||
os.path.join(os.environ['JUPYTERHUB_CONFIG_DIR'], 'spawn_notebook.sh')] | ||
|
||
self.log.info(f'Executing command: {" ".join(cmd)}') | ||
subprocess.run(cmd, check=True) | ||
|
||
def _ensure_system_user(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mostly copied from customer_spawner.py. But using |
||
""" | ||
Create a system user with the given username if it does not already exist. | ||
Ensure the group exists before creating the user. | ||
Use a file lock to prevent race conditions. | ||
""" | ||
|
||
lock_file = os.path.join(tempfile.gettempdir(), f'user_creation_{self.username}.lock') | ||
|
||
with open(lock_file, 'w') as lock: | ||
fcntl.flock(lock, fcntl.LOCK_EX) | ||
try: | ||
# Check if user already exists | ||
try: | ||
pwd.getpwnam(self.username) | ||
self.log.info(f'User {self.username} already exists') | ||
return | ||
except KeyError: | ||
|
||
# Create the user | ||
self.log.info(f'Creating system user: {self.username}') | ||
useradd_cmd = ['sudo', 'useradd', '-r'] | ||
|
||
if self.groupname: | ||
# Check if the group exists, create if necessary | ||
try: | ||
grp.getgrnam(self.groupname) | ||
self.log.info(f'Group {self.groupname} already exists') | ||
except KeyError: | ||
self.log.info(f'Group {self.groupname} does not exist, creating it.') | ||
subprocess.run(['sudo', 'groupadd', self.groupname], check=True) | ||
|
||
useradd_cmd.extend(['-g', self.groupname]) | ||
|
||
useradd_cmd.append(self.username) | ||
|
||
self.log.info(f'Creating system user: {self.username}') | ||
subprocess.run(useradd_cmd, check=True) | ||
|
||
except subprocess.CalledProcessError as e: | ||
raise ValueError(f'Failed to create system user: {e}') | ||
|
||
finally: | ||
fcntl.flock(lock, fcntl.LOCK_UN) | ||
|
||
def _ensure_workspace_permission(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. copied from custom_spawner.py |
||
""" | ||
Ensure the user's workspace has the correct permissions. | ||
""" | ||
try: | ||
user_info = pwd.getpwnam(self.username) | ||
except KeyError: | ||
raise ValueError(f'System user {self.username} does not exist') | ||
gid = user_info.pw_gid | ||
group_name = grp.getgrgid(gid).gr_name | ||
|
||
self.log.info(f'Configuring workspace permissions for {self.username}') | ||
subprocess.run(['sudo', 'chown', '-R', f'{self.username}:{group_name}', self.user_dir], check=True) | ||
subprocess.run(['sudo', 'chmod', '-R', '750', self.user_dir], check=True) | ||
|
||
def _init_logger(self): | ||
""" | ||
Initializes a logger for tracking the operations performed for the user. | ||
""" | ||
logger = logging.getLogger(self.username) | ||
handler = logging.StreamHandler() | ||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | ||
handler.setFormatter(formatter) | ||
logger.addHandler(handler) | ||
logger.setLevel(logging.INFO) | ||
return logger | ||
|
||
|
||
def main(): | ||
print("Starting hub_singleuser.py...") | ||
|
||
username = os.getenv('JUPYTERHUB_USER') | ||
|
||
if not username: | ||
raise ValueError('JUPYTERHUB_USER environment variable not set') | ||
|
||
single_user_manager = SingleUserEnvManager(username) | ||
single_user_manager.setup_environment() | ||
single_user_manager.start_single_user_server() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from jupyterhub_config.hub_singleuser import * | ||
|
||
def test_noop(): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why the juipyterhub username and the linux username need to be the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
somewhere down the line (I think it's auth) will need to check that system user exist with the same jupyterhub username.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite get that - why would the linux username need to match the auth username?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe for some spawner it's not required. But I don't see it concerns us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://discourse.jupyter.org/t/is-authentication-without-system-users-possible/1770
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not if it's bypassing fair share it isn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will explore some yarn setting (
spark.yarn.queue
) on the client's side.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you just use the same user for the containers you don't have to worry about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created another issue for this. #97
It's really not how Jupyterhub works as far as I understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're saying that JH requires the linux username to match the JH username then yeah, that won't work and we'll have to look into modifying yarn or how the submit works