From 9df86c44864f64b0521fab56277ffd1863cd9723 Mon Sep 17 00:00:00 2001 From: Tom Lehmann Date: Wed, 5 Aug 2026 16:46:36 +0200 Subject: [PATCH] Let linuxmusterTools import on a machine without samba and sophomorix lmnconfig/samba.py ran `/usr/bin/net conf list` as an unguarded module-level side effect, and lmnconfig/sophomorix.py read the ROLE_USER section of a sophomorix.ini that need not exist. Importing anything from linuxmusterTools.linbo reaches both through linbo.config -> devices -> lmnconfig, so on any machine without a configured linuxmuster.net the whole package failed to import with FileNotFoundError or KeyError. That is what stops the pytests from running anywhere but a server: the lmnconfig suite does not fail, it fails to collect. test_samba.py says so in its own module docstring, and no workflow runs the tests. Both sites now behave like the code directly next to them, which already guarded the same class of problem: the smb.conf read in samba.py sits in a try/except, and SchoolConfig checks os.path.isfile before opening. A missing or failing `net` leaves SHARES_LIST and DFS empty and logs the reason; a missing sophomorix.ini leaves the role lists empty and warns. Behaviour on a configured server is unchanged. The 38 existing lmnconfig tests pass, and three cover the new paths. --- .../lmnconfig/pytests/test_samba.py | 36 +++++++++++++++++++ .../lmnconfig/pytests/test_sophomorix.py | 19 ++++++++++ .../linuxmusterTools/lmnconfig/samba.py | 8 +++-- .../linuxmusterTools/lmnconfig/sophomorix.py | 4 ++- 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_samba.py b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_samba.py index 4de8f772..c848867d 100644 --- a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_samba.py +++ b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_samba.py @@ -12,6 +12,10 @@ independent of any of that, so it is fully covered below. """ +import importlib +from subprocess import CalledProcessError + +import linuxmusterTools.lmnconfig.samba as samba_module from linuxmusterTools.lmnconfig.samba import parse_log_level @@ -46,3 +50,35 @@ def test_last_bare_int_wins_for_general(): def test_extra_whitespace_is_ignored(): result = parse_log_level(' 1 auth_audit:2 ') assert result == {'general': 1, 'auth_audit': 2} + + +# --------------------------------------------------------------------------- +# Import without a Samba installation +# --------------------------------------------------------------------------- + +def _reload_samba_with_check_output(monkeypatch, raising): + """Re-execute samba.py with a check_output that fails the given way.""" + import subprocess + + monkeypatch.setattr(subprocess, 'check_output', raising) + return importlib.reload(samba_module) + + +def test_import_survives_missing_net_binary(monkeypatch): + def _no_binary(*args, **kwargs): + raise FileNotFoundError(2, 'No such file or directory', '/usr/bin/net') + + reloaded = _reload_samba_with_check_output(monkeypatch, _no_binary) + + assert reloaded.SHARES_LIST == [] + assert reloaded.DFS == {} + + +def test_import_survives_failing_net_call(monkeypatch): + def _fails(*args, **kwargs): + raise CalledProcessError(1, ['/usr/bin/net', 'conf', 'list']) + + reloaded = _reload_samba_with_check_output(monkeypatch, _fails) + + assert reloaded.SHARES_LIST == [] + assert reloaded.DFS == {} diff --git a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_sophomorix.py b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_sophomorix.py index 141eb8a8..2ef1b8cd 100644 --- a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_sophomorix.py +++ b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/pytests/test_sophomorix.py @@ -234,3 +234,22 @@ def test_sophomorix_conf_file_found_reads_data_attribute(monkeypatch, fake_lmnfi # Note: SophomorixConf reads config.data (not config.read()), unlike the # other classes in this module. assert config.data == canned + + +# --------------------------------------------------------------------------- +# SophomorixIni without a sophomorix installation +# --------------------------------------------------------------------------- + +def test_sophomorix_ini_without_role_user_section_gives_empty_roles(monkeypatch): + # ConfigParser.read() silently ignores a missing file, so an unconfigured + # machine leaves the parser with nothing but the DEFAULT section. Reading + # ROLE_USER unguarded raised KeyError and made the whole package + # unimportable off-server. + monkeypatch.setattr( + sophomorix_module.ConfigParser, 'read', lambda self, *args, **kwargs: [] + ) + + ini = SophomorixIni() + + assert ini.userrole == [] + assert ini.computerrole == [] diff --git a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/samba.py b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/samba.py index b1d907ac..ec7eabb6 100644 --- a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/samba.py +++ b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/samba.py @@ -2,7 +2,7 @@ import os from configparser import ConfigParser from configobj import ConfigObj -from subprocess import check_output +from subprocess import CalledProcessError, check_output from io import StringIO @@ -57,7 +57,11 @@ def parse_log_level(level): DFS = {} -config = ConfigObj(StringIO(check_output(["/usr/bin/net", "conf", "list"], shell=False).decode())) +try: + config = ConfigObj(StringIO(check_output(["/usr/bin/net", "conf", "list"], shell=False).decode())) +except (OSError, CalledProcessError) as e: + logger.error(f"Can not read the samba share configuration: {str(e)}. Is linuxmuster.net installed and configured ?") + config = ConfigObj() SHARES_LIST = list(config.keys()) diff --git a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/sophomorix.py b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/sophomorix.py index a25bce9a..2993f8dc 100644 --- a/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/sophomorix.py +++ b/usr/lib/python3/dist-packages/linuxmusterTools/lmnconfig/sophomorix.py @@ -36,6 +36,8 @@ class SophomorixIni: def __init__(self): self.path = "/usr/share/sophomorix/devel/sophomorix.ini" self.data = ConfigParser(delimiters=("=",), dict_type=MultiOrderedDict, strict=False) + if not os.path.isfile(self.path): + logger.warning(f"No sophomorix ini found at {self.path}. Is sophomorix installed and configured ?") self.data.read(self.path) self.sections = list(self.data.keys()) @@ -56,7 +58,7 @@ def __init__(self): 'thinclient', 'iponly', ] - self.userrole = list(self.dict['ROLE_USER'].keys()) + self.userrole = list(self.dict.get('ROLE_USER', {}).keys()) @staticmethod def sanitize(value):