diff --git a/src/simplezfs/zfs_cli.py b/src/simplezfs/zfs_cli.py index c3e55a3..911aa78 100644 --- a/src/simplezfs/zfs_cli.py +++ b/src/simplezfs/zfs_cli.py @@ -30,8 +30,9 @@ class ZFSCli(ZFS): If ``zfs_exe`` is supplied, it is assumed that it points to the path of the ``zfs(8)`` executable. ''' def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[PEHelperBase] = None, - use_pe_helper: bool = False, zfs_exe: Optional[str] = None, **kwargs) -> None: - super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, use_pe_helper=use_pe_helper, + pe_helper_mode: PEHelperMode = PEHelperMode.DO_NOT_USE, zfs_exe: Optional[str] = None, + **kwargs) -> None: + super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, pe_helper_mode=pe_helper_mode, **kwargs) self.find_executable(path=zfs_exe) diff --git a/src/simplezfs/zpool.py b/src/simplezfs/zpool.py index 7da032a..b364936 100644 --- a/src/simplezfs/zpool.py +++ b/src/simplezfs/zpool.py @@ -1,9 +1,15 @@ +''' +ZPOOL frontend API +''' + import logging import os import stat from typing import Optional +from .pe_helper import PEHelperBase +from .types import PEHelperMode log = logging.getLogger('simplezfs.zpool') @@ -20,12 +26,25 @@ class ZPool: * Using the native API When creating an instance of this class, select one or the other as the ``api`` argument. + + + **Properties and Metadata** + + Please see the documentation for :class:`~simplezfs.zfs.ZFS` for native and metadata properties. + + :param metadata_namespace: Default namespace + :param pe_helper: Privilege escalation (PE) helper to use for actions that require elevated privileges (root). + :param pe_helper_mode: How and when to use the PEHelper. Defaults to not using it at all. + :param kwargs: Extra arguments, ignored ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, - use_pe_helper: bool = False, **kwargs) -> None: + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[PEHelperBase] = None, + pe_helper_mode: PEHelperMode = PEHelperMode.DO_NOT_USE, **kwargs) -> None: self.metadata_namespace = metadata_namespace self.pe_helper = pe_helper - self.use_pe_helper = use_pe_helper + self.pe_helper_mode = pe_helper_mode + + def __repr__(self) -> str: + return f'' @property def metadata_namespace(self) -> Optional[str]: @@ -44,36 +63,37 @@ class ZPool: self._metadata_namespace = namespace @property - def pe_helper(self) -> Optional[str]: + def pe_helper(self) -> Optional[PEHelperBase]: ''' Returns the pe_helper, which may be None if not set. ''' return self._pe_helper @pe_helper.setter - def pe_helper(self, helper: Optional[str]) -> None: + def pe_helper(self, helper: Optional[PEHelperBase]) -> None: ''' - Sets the privilege escalation (PE) helper. Some basic checks for existance and executability are performed, - but these are not sufficient for secure operation and are provided to aid the user in configuring the library. - - :note: This method does not follow symlinks. - - :raises FileNotFoundError: if the helper can't be found or is not executable. + Sets the privilege escalation (PE) helper. Supply ``None`` to unset it. ''' if helper is None: log.debug('PE helper is None') - self._pe_helper = None - else: - candidate = helper.strip() - - mode = os.lstat(candidate).st_mode - if not stat.S_ISREG(mode): - raise FileNotFoundError('PE helper must be a file') - if not os.access(candidate, os.X_OK): - raise FileNotFoundError('PE helper must be executable') - log.debug(f'Setting privilege escalation helper to "{candidate}"') - self._pe_helper = candidate + self._pe_helper = helper + @property + def pe_helper_mode(self) -> PEHelperMode: + ''' + Returns whether the privilege escalation (PE) helper should be used and when. If the helper has not been set, + this property evaluates to ``False``. + ''' + if self._pe_helper is None: + return PEHelperMode.DO_NOT_USE + return self._pe_helper_mode + + @pe_helper_mode.setter + def pe_helper_mode(self, mode: PEHelperMode) -> None: + ''' + Sets the privilege escalation (PE) helper mode. + ''' + self._pe_helper_mode = mode def get_zpool(api: str = 'cli', metadata_namespace: Optional[str] = None, **kwargs) -> ZPool: ''' diff --git a/src/simplezfs/zpool_cli.py b/src/simplezfs/zpool_cli.py index 8e53b2d..8ab6dd2 100644 --- a/src/simplezfs/zpool_cli.py +++ b/src/simplezfs/zpool_cli.py @@ -7,7 +7,9 @@ import logging import shutil from typing import Any, Dict, Optional -from .types import ZPoolHealth + +from .pe_helper import PEHelperBase +from .types import PEHelperMode, ZPoolHealth from .zpool import ZPool log = logging.getLogger('simplezfs.zpool_cli') @@ -21,18 +23,23 @@ class ZPoolCli(ZPool): If ``zpool_exe`` is supplied, it is assumed that it points to the path to the ``zpool(8)`` executable. ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, - use_pe_helper: bool = False, zpool_exe: Optional[str] = None, **kwargs) -> None: - super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, use_pe_helper=use_pe_helper) + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[PEHelperBase] = None, + pe_helper_mode: PEHelperMode = PEHelperMode.DO_NOT_USE, zpool_exe: Optional[str] = None, + **kwargs) -> None: + super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, pe_helper_mode=pe_helper_mode, + **kwargs) self.find_executable(path=zpool_exe) + def __repr__(self) -> str: + return f'' + def find_executable(self, path: str = None) -> None: ''' Tries to find the executable ``zpool``. If ``path`` points to an executable, it is used instead of relying on the PATH to find it. It does not fall back to searching in PATH if ``path`` does not point to an exeuctable. An exception is raised if no executable could be found. - :param path: Path to an executable to use instead of searching through $PATH. + :param path: Path to the executable, used blindly if supplied. :raises OSError: If the executable could not be found. ''' exe_path = path @@ -40,14 +47,14 @@ class ZPoolCli(ZPool): exe_path = shutil.which('zpool') if not exe_path: - raise OSError('Could not find the executable') + raise OSError('Could not find executable') self.__exe = exe_path @property def executable(self) -> str: ''' - Returns the executable found by find_executable. + Returns the zpool executable that was found by find_executable. ''' return self.__exe diff --git a/tests/test_zpool_cli.py b/tests/test_zpool_cli.py index 46fd3c2..a64b246 100644 --- a/tests/test_zpool_cli.py +++ b/tests/test_zpool_cli.py @@ -39,7 +39,7 @@ class TestZPoolCli: with pytest.raises(OSError) as excinfo: ZPoolCli() - assert 'not find the executable' in str(excinfo.value) + assert 'not find executable' in str(excinfo.value) ##########################################################################