diff --git a/src/serverfs/usr/share/linuxmuster/linbo/linbo_mkgrubimg.py b/src/serverfs/usr/share/linuxmuster/linbo/linbo_mkgrubimg.py index b0c80a7..bc4ce09 100755 --- a/src/serverfs/usr/share/linuxmuster/linbo/linbo_mkgrubimg.py +++ b/src/serverfs/usr/share/linuxmuster/linbo/linbo_mkgrubimg.py @@ -9,7 +9,7 @@ # linbo-remote/linbo_remote_cli.py. # Signed-off by: thomas@linuxmuster.net # Assisted by : Claude -# Date : 20260903 +# Date : 20261001 # import configparser @@ -56,6 +56,30 @@ def getGrubEfiModules(efi_dir='/usr/lib/grub/x86_64-efi'): return GRUB_COMMON_MODULES + ' ' + ' '.join(efi_modules) +def getSchoolFromDevicesFile(wsfile): + """ + School a workstations file belongs to, by sophomorix's path convention + (same as linuxmuster-base7's readDevicesCsv()): a non-default school's + file is /.devices.csv, default-school's is devices.csv. + """ + basename = os.path.basename(wsfile) + if basename.endswith('.devices.csv'): + return basename[:-len('.devices.csv')] + return 'default-school' + + +def getQualifiedHostname(school, hostname, csv_hostname): + """ + Host name as it appears in hostcfg and dhcp: unchanged for default-school, + - for another school. The name keeps the case of the + devices.csv row, because linuxmuster-import-devices writes the prefixed + entries that way (getHostname() returns the lowercased name). + """ + if school == 'default-school': + return hostname + return school + '-' + csv_hostname + + def usage(): print('Purpose: linbo-mkgrubimg creates host specific image for grub network') print('boot and stores it in /srv/linbo/boot/grub/hostcfg/.img.') @@ -68,7 +92,9 @@ def usage(): print(' workstations file.') print(' -w , --workstations= : path to workstations file, default is') print(' /etc/linuxmuster/sophomorix') - print(' /default-school/devices.csv.') + print(' /default-school/devices.csv. For') + print(' another school use its') + print(' /.devices.csv.') def main(argv=None): @@ -86,14 +112,14 @@ def main(argv=None): # default values setfilename = False wsfile = environment.WIMPORTDATA - hostname = None + search = None # evaluate options for o, a in opts: if o in ("-s", "--setfilename"): setfilename = True elif o in ("-n", "--name"): - hostname, hostrow = getHostname(wsfile, a) + search = a elif o in ("-w", "--workstations"): if os.path.isfile(a): wsfile = a @@ -106,10 +132,17 @@ def main(argv=None): else: assert False, "unhandled option" - # evaluate hostname + # evaluate hostname, only now that -w is known whatever the option order + hostname = None + if search is not None: + hostname, hostrow = getHostname(wsfile, search) if hostname is None: usage() sys.exit(1) + # hosts of a non-default school carry a - prefix in dhcp and + # hostcfg, as linuxmuster-import-devices writes them + school = getSchoolFromDevicesFile(wsfile) + hostname = getQualifiedHostname(school, hostname, hostrow[1]) # grub image filename img = environment.LINBOGRUBDIR + '/hostcfg/' + hostname + '.img' @@ -205,7 +238,7 @@ def main(argv=None): foption = 'filename "' + imgrel + '"' # modify workstations file row_old = field1 + ';' + field2 + ';' + group + ';' + mac + ';' + ip + ';' + field6 + ';' + field7 + ';' + field8 + ';' + field9 + ';' + field10 + ';' + field11 - row_new = field1 + ';' + hostname + ';' + group + ';' + mac + ';' + ip + ';' + field6 + ';' + field7 + ';' + foption + ';' + field9 + ';' + field10 + ';' + field11 + row_new = field1 + ';' + field2 + ';' + group + ';' + mac + ';' + ip + ';' + field6 + ';' + field7 + ';' + foption + ';' + field9 + ';' + field10 + ';' + field11 rc, content = readTextfile(wsfile) rc = writeTextfile(wsfile, content.replace(row_old, row_new), 'w') # modify dhcp device entry @@ -222,7 +255,7 @@ def main(argv=None): if 'host ' + hostname in content: # replace device entry with custom grub img path row_old = re.findall('host ' + hostname + ' .*?(?=}|$)', content, re.DOTALL)[0] - row_new = 'host ' + hostname + ' {\n hardware ethernet ' + mac + ';\n fixed-address ' + ip + ';\n ' + foption + ';\n option host-name "' + hostname + '";\n option extensions-path "' + group + '";\n' + row_new = 'host ' + hostname + ' {\n hardware ethernet ' + mac + ';\n fixed-address ' + ip + ';\n ' + foption + ';\n option host-name "' + hostname + '";\n option extensions-path "' + group + '";\n option nis-domain "' + group + '";\n' row_new = row_new.replace(' fixed-address 0.0.0.0;\n', '') rc = writeTextfile(item, content.replace(row_old, row_new), 'w') break diff --git a/tests/python/test_linbo_mkgrubimg.py b/tests/python/test_linbo_mkgrubimg.py new file mode 100644 index 0000000..2d0006f --- /dev/null +++ b/tests/python/test_linbo_mkgrubimg.py @@ -0,0 +1,63 @@ +# +# Filename : test_linbo_mkgrubimg.py +# Description : pytest tests for linbo_mkgrubimg.py's pure helpers - the +# school lookup from the workstations file path and the school +# qualified host name (issue #178). +# Signed-off by: tom.lehmann@netzint.de +# Assisted by : Claude +# Date : 20261001 +# + +import importlib +import sys +import types + +import pytest + + +@pytest.fixture +def mkgrubimg(monkeypatch): + """ + linbo_mkgrubimg imports linuxmuster-base7 at module level, which isn't + installed in the test environment - stub the names it imports (none of + which the helpers under test call), scoped to the test so the stub doesn't + leak into tests that rely on base7 being absent. + """ + base7 = types.ModuleType('linuxmuster_base7') + functions = types.ModuleType('linuxmuster_base7.functions') + for name in ('getHostname', 'getStartconfOption', 'readTextfile', 'writeTextfile'): + setattr(functions, name, None) + base7.functions = functions + monkeypatch.setitem(sys.modules, 'linuxmuster_base7', base7) + monkeypatch.setitem(sys.modules, 'linuxmuster_base7.functions', functions) + monkeypatch.delitem(sys.modules, 'linbo_mkgrubimg', raising=False) + return importlib.import_module('linbo_mkgrubimg') + + +def test_default_school_devices_file(mkgrubimg): + assert mkgrubimg.getSchoolFromDevicesFile('/etc/linuxmuster/sophomorix/default-school/devices.csv') == 'default-school' + + +def test_non_default_school_devices_file(mkgrubimg): + assert mkgrubimg.getSchoolFromDevicesFile('/etc/linuxmuster/sophomorix/abc/abc.devices.csv') == 'abc' + + +def test_school_name_with_hyphen(mkgrubimg): + assert mkgrubimg.getSchoolFromDevicesFile('/etc/linuxmuster/sophomorix/gs-nord/gs-nord.devices.csv') == 'gs-nord' + + +def test_relative_path(mkgrubimg): + assert mkgrubimg.getSchoolFromDevicesFile('abc.devices.csv') == 'abc' + assert mkgrubimg.getSchoolFromDevicesFile('devices.csv') == 'default-school' + + +def test_default_school_host_name_is_unchanged(mkgrubimg): + assert mkgrubimg.getQualifiedHostname('default-school', 'pc01', 'PC01') == 'pc01' + + +def test_non_default_school_host_name_keeps_the_case_of_the_devices_row(mkgrubimg): + assert mkgrubimg.getQualifiedHostname('agy', 'pc02', 'PC02') == 'agy-PC02' + + +def test_non_default_school_host_name_with_lowercase_row(mkgrubimg): + assert mkgrubimg.getQualifiedHostname('gs-nord', 'pc02', 'pc02') == 'gs-nord-pc02'