Skip to content

Commit

Permalink
qwAdd option to configure GID for scratch area
Browse files Browse the repository at this point in the history
  • Loading branch information
callumforrester committed Dec 19, 2024
1 parent ed59078 commit 80b0a42
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 5 deletions.
20 changes: 17 additions & 3 deletions src/blueapi/cli/scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from git import Repo

from blueapi.config import ScratchConfig
from blueapi.utils import is_sgid_enabled
from blueapi.utils import get_owner_gid, is_sgid_enabled

_DEFAULT_INSTALL_TIMEOUT: float = 300.0

Expand All @@ -25,7 +25,7 @@ def setup_scratch(
install_timeout: Timeout for installing packages
"""

_validate_root_directory(config.root)
_validate_root_directory(config.root, config.required_gid)

logging.info(f"Setting up scratch area: {config.root}")

Expand Down Expand Up @@ -96,7 +96,7 @@ def scratch_install(path: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> No
raise RuntimeError(f"Failed to install {path}: Exit Code: {process.returncode}")


def _validate_root_directory(root_path: Path) -> None:
def _validate_root_directory(root_path: Path, required_gid: int | None) -> None:
_validate_directory(root_path)

if not is_sgid_enabled(root_path):
Expand All @@ -111,6 +111,20 @@ def _validate_root_directory(root_path: Path) -> None:
enable the SGID bit.
""")
)
elif required_gid is not None and get_owner_gid(root_path) != required_gid:
raise PermissionError(
textwrap.dedent(f"""
The configuration requires that {root_path} be owned by the group with
ID {required_gid}.
You may be able to find this group's name by running the following
in the terminal.
getent group 1000 | cut -d: -f1
You can transfer ownership, if you have sufficient permissions, with the chown
command.
""")
)


def _validate_directory(path: Path) -> None:
Expand Down
1 change: 1 addition & 0 deletions src/blueapi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class ScratchRepository(BlueapiBaseModel):

class ScratchConfig(BlueapiBaseModel):
root: Path = Path("/tmp/scratch/blueapi")
required_gid: int | None = None
repositories: list[ScratchRepository] = Field(default_factory=list)


Expand Down
3 changes: 2 additions & 1 deletion src/blueapi/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .base_model import BlueapiBaseModel, BlueapiModelConfig, BlueapiPlanModelConfig
from .connect_devices import connect_devices
from .file_permissions import is_sgid_enabled
from .file_permissions import get_owner_gid, is_sgid_enabled
from .invalid_config_error import InvalidConfigError
from .modules import load_module_all
from .serialization import serialize
Expand All @@ -16,4 +16,5 @@
"InvalidConfigError",
"connect_devices",
"is_sgid_enabled",
"get_owner_gid",
]
13 changes: 13 additions & 0 deletions src/blueapi/utils/file_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ def is_sgid_enabled(path: Path) -> bool:

mask = os.stat(path).st_mode
return bool(mask & stat.S_ISGID)


def get_owner_gid(path: Path) -> int:
"""Get the GID of the owner of a file
Args:
path: Path to the file to check
Returns:
bool: The GID of the file owner
"""

return os.stat(path).st_gid
27 changes: 27 additions & 0 deletions tests/unit_tests/cli/test_scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from blueapi.cli.scratch import ensure_repo, scratch_install, setup_scratch
from blueapi.config import ScratchConfig, ScratchRepository
from blueapi.utils import get_owner_gid


@pytest.fixture
Expand Down Expand Up @@ -166,6 +167,32 @@ def test_setup_scratch_fails_on_non_sgid_root(
setup_scratch(config)


def test_setup_scratch_fails_on_wrong_gid(
directory_path: Path,
):
config = ScratchConfig(
root=directory_path,
required_gid=12345,
repositories=[],
)
assert get_owner_gid(directory_path) != 12345
with pytest.raises(PermissionError):
setup_scratch(config)


def test_setup_scratch_succeeds_on_required_gid(
directory_path: Path,
):
os.chown(directory_path, uid=12345, gid=12345)
config = ScratchConfig(
root=directory_path,
required_gid=12345,
repositories=[],
)
assert get_owner_gid(directory_path) == 12345
setup_scratch(config)


@patch("blueapi.cli.scratch.ensure_repo")
@patch("blueapi.cli.scratch.scratch_install")
def test_setup_scratch_iterates_repos(
Expand Down
9 changes: 8 additions & 1 deletion tests/unit_tests/utils/test_file_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from blueapi.utils import is_sgid_enabled
from blueapi.utils import get_owner_gid, is_sgid_enabled


@pytest.mark.parametrize(
Expand Down Expand Up @@ -50,3 +50,10 @@ def _mocked_is_sgid_enabled(mock_stat: Mock, bits: int) -> bool:
(mock_stat_for_file := Mock()).st_mode = bits
mock_stat.return_value = mock_stat_for_file
return is_sgid_enabled(Path("/doesnt/matter"))


@patch("blueapi.utils.file_permissions.os.stat")
def test_get_owner_gid(mock_stat: Mock):
(mock_stat_for_file := Mock()).st_gid = 12345
mock_stat.return_value = mock_stat_for_file
assert get_owner_gid(Path("/doesnt/matter")) == 12345

0 comments on commit 80b0a42

Please sign in to comment.