Skip to content

Commit

Permalink
add create system user
Browse files Browse the repository at this point in the history
  • Loading branch information
Tianhao-Gu committed Sep 5, 2024
1 parent a182063 commit 92c218b
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 2 deletions.
43 changes: 42 additions & 1 deletion src/jupyterhub_config/custom_spawner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import subprocess

from jupyterhub.spawner import SimpleLocalProcessSpawner


Expand All @@ -14,4 +16,43 @@ def start(self):
variables, and sets the notebook directory before starting the server.
"""

return super().start()
username = self.user.name

# Ensure the system user exists
self._ensure_system_user(username, group='jupyterhub')

return super().start()

def _ensure_system_user(self, username: str, group: str = None):
"""
Create a system user with the given username if it does not already exist.
Ensure the group exists before creating the user.
"""
try:
# Check if user already exists
result = subprocess.run(['id', username], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode == 0:
self.log.info(f'User {username} already exists')
return

# Create the user
self.log.info(f'Creating system user: {username}')
useradd_cmd = ['sudo', 'useradd', '-r']

if group:
# Check if the group exists, create if necessary
group_check = subprocess.run(['getent', 'group', group], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if group_check.returncode != 0:
self.log.info(f'Group {group} does not exist, creating it.')
subprocess.run(['sudo', 'groupadd', group], check=True)
else:
self.log.info(f'Group {group} already exists')

useradd_cmd.extend(['-g', group])

useradd_cmd.append(username)

subprocess.run(useradd_cmd, check=True)

except subprocess.CalledProcessError as e:
raise ValueError(f'Failed to create system user: {e}')
84 changes: 83 additions & 1 deletion test/src/jupyterhub_config/custom_spawner_test.py
Original file line number Diff line number Diff line change
@@ -1 +1,83 @@
from jupyterhub_config.custom_spawner import *
import subprocess
from subprocess import CalledProcessError
from unittest.mock import patch, MagicMock

import pytest

from jupyterhub_config.custom_spawner import VirtualEnvSpawner


@pytest.fixture
def mock_spawner():
spawner = VirtualEnvSpawner()
spawner.log = MagicMock() # Mock the logger
return spawner


# Test when the user already exists
@patch('subprocess.run')
def test_ensure_system_user_already_exists(mock_run, mock_spawner):
# Mock 'id' command to simulate user already exists
mock_run.return_value.returncode = 0

username = 'testuser'
mock_spawner._ensure_system_user(username)

mock_spawner.log.info.assert_called_with(f'User {username} already exists')
mock_run.assert_called_once_with(['id', username], stdout=subprocess.PIPE, stderr=subprocess.PIPE)


# Test when the group and user need to be created
@patch('subprocess.run')
def test_ensure_system_user_create_group_and_user(mock_run, mock_spawner):
# Mock the 'id' and 'getent' command returncodes
def mock_run_side_effect(cmd, *args, **kwargs):
if cmd == ['id', 'testuser']:
return MagicMock(returncode=1) # User does not exist
elif cmd == ['getent', 'group', 'testgroup']:
return MagicMock(returncode=2) # Group does not exist
return MagicMock(returncode=0) # Successful creation

mock_run.side_effect = mock_run_side_effect

username = 'testuser'
group = 'testgroup'
mock_spawner._ensure_system_user(username, group)

mock_spawner.log.info.assert_any_call(f'Creating system user: {username}')
mock_spawner.log.info.assert_any_call(f'Group {group} does not exist, creating it.')
mock_run.assert_any_call(['sudo', 'groupadd', group], check=True)
mock_run.assert_any_call(['sudo', 'useradd', '-r', '-g', group, username], check=True)


# Test when the user is created without a group
@patch('subprocess.run')
def test_ensure_system_user_create_user_without_group(mock_run, mock_spawner):
# Mock the 'id' command to simulate user does not exist
mock_run.side_effect = [
MagicMock(returncode=1), # User does not exist
MagicMock(returncode=0) # User created successfully
]

username = 'testuser'
mock_spawner._ensure_system_user(username)

mock_spawner.log.info.assert_any_call(f'Creating system user: {username}')
mock_run.assert_called_with(['sudo', 'useradd', '-r', username], check=True)


# Test subprocess.CalledProcessError is handled correctly
@patch('subprocess.run')
def test_ensure_system_user_error(mock_run, mock_spawner):
# Mock the 'id' command to simulate user does not exist
# Mock 'useradd' command to raise CalledProcessError
mock_run.side_effect = [
MagicMock(returncode=1),
CalledProcessError(1, 'useradd')
]

with pytest.raises(ValueError, match="Failed to create system user"):
mock_spawner._ensure_system_user('testuser')

mock_run.assert_any_call(['id', 'testuser'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
mock_run.assert_any_call(['sudo', 'useradd', '-r', 'testuser'], check=True)

0 comments on commit 92c218b

Please sign in to comment.