diff --git a/sos/policies/distros/debian.py b/sos/policies/distros/debian.py index 6f3cbc510b..bb15c4a9e5 100644 --- a/sos/policies/distros/debian.py +++ b/sos/policies/distros/debian.py @@ -44,6 +44,7 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, self.package_manager = DpkgPackageManager(chroot=self.sysroot, remote_exec=remote_exec) self.valid_subclasses += [DebianPlugin] + self.load_presets() def _get_pkg_name_for_binary(self, binary): # for binary not specified inside {..}, return binary itself diff --git a/sos/policies/distros/ubuntu.py b/sos/policies/distros/ubuntu.py index 8bc0962faa..8a7a59528e 100644 --- a/sos/policies/distros/ubuntu.py +++ b/sos/policies/distros/ubuntu.py @@ -14,6 +14,8 @@ from sos.policies.package_managers.dpkg import DpkgPackageManager from sos.policies.package_managers import MultiPackageManager +from sos.presets.ubuntu import SUNBEAM, UBUNTU_PRESETS, UBUNTU + class UbuntuPolicy(DebianPolicy): vendor = "Canonical" @@ -51,6 +53,12 @@ def __init__(self, sysroot=None, init=None, probe_runtime=True, pass self.valid_subclasses += [UbuntuPlugin] + self.register_presets(UBUNTU_PRESETS) + + def probe_preset(self): + if self.pkg_by_name("sunbeam") is not None: + return self.find_preset(SUNBEAM) + return self.find_preset(UBUNTU) def dist_version(self): """ Returns the version stated in DISTRIB_RELEASE diff --git a/sos/presets/ubuntu/__init__.py b/sos/presets/ubuntu/__init__.py new file mode 100644 index 0000000000..e727b58892 --- /dev/null +++ b/sos/presets/ubuntu/__init__.py @@ -0,0 +1,36 @@ +# Copyright (C) 2026 Canonical Ltd., Arif Ali + +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + +from sos.options import SoSOptions +from sos.presets import PresetDefaults + + +UBUNTU = "ubuntu" +UBUNTU_DESC = "Ubuntu" + +SUNBEAM = "sunbeam" +SUNBEAM_DESC = "Canonical OpenStack" +SUNBEAM_OPTS = SoSOptions( + all_logs=True, + plugopts=[ + 'sunbeam.juju-allow-login = True', + 'kubernetes.all = True', + 'kubernetes.describe = True', + 'kubernetes.kubelogs = True', + 'kubernetes.podlogs = True', + ]) + +UBUNTU_PRESETS = { + SUNBEAM: PresetDefaults(name=SUNBEAM, desc=SUNBEAM_DESC, + opts=SUNBEAM_OPTS), + UBUNTU: PresetDefaults(name=UBUNTU, desc=UBUNTU_DESC), +} + +# vim: set et ts=4 sw=4 : diff --git a/sos/report/__init__.py b/sos/report/__init__.py index 20f314c0d2..6771009c4a 100644 --- a/sos/report/__init__.py +++ b/sos/report/__init__.py @@ -927,11 +927,13 @@ def _set_tunables(self): opts = {} for opt in self.opts.plugopts: try: - opt, val = opt.split("=") + opt, val = opt.split("=", 1) except ValueError: val = True + opt = opt.strip() if isinstance(val, str): + val = val.strip() arg = val.lower() if arg in ["on", "enable", "enabled", "true", "yes"]: val = True diff --git a/tests/unittests/report_tests.py b/tests/unittests/report_tests.py index 1f4c400ba5..c070fc2e89 100644 --- a/tests/unittests/report_tests.py +++ b/tests/unittests/report_tests.py @@ -5,6 +5,7 @@ # version 2 of the GNU General Public License. # # See the LICENSE file in the source distribution for further information. +import logging import unittest try: @@ -12,8 +13,12 @@ except ImportError: import simplejson as json +from sos.report import SoSReport +from sos.report.plugins import Plugin, PluginOpt from sos.report.reporting import (Report, Section, Command, CopiedFile, CreatedFile, Alert, PlainTextReport) +from sos.policies.distros import LinuxPolicy +from sos.policies.init_systems import InitSystem class ReportTest(unittest.TestCase): @@ -149,6 +154,73 @@ def test_alert(self): PlainTextReport(self.report).unicode()) +class MockOptions: + all_logs = False + dry_run = False + log_size = 25 + allow_system_changes = False + skip_commands = [] + skip_files = [] + plugopts = [] + + +class MockPlugin(Plugin): + + option_list = [ + PluginOpt('baz', default=False), + PluginOpt('empty', default=None), + PluginOpt('test_option', default='foobar', val_type=str) + ] + + def __init__(self, commons): + super().__init__(commons=commons) + + +class SetTunablesPresetSpacesTest(unittest.TestCase): + + def setUp(self): + self.commons = { + 'sysroot': '/', + 'policy': LinuxPolicy(init=InitSystem()), + 'cmdlineopts': MockOptions(), + 'devices': {} + } + self.plugin = MockPlugin(self.commons) + + self.report = SoSReport.__new__(SoSReport) + self.report.opts = MockOptions() + self.report.soslog = logging.getLogger('sos_test_set_tunables') + self.report.loaded_plugins = [('mock', self.plugin)] + + def set_tunables(self, plugopts): + """Run _set_tunables with the given preset-style plugopts list.""" + self.report.opts.plugopts = plugopts + self.report._set_tunables() + + def test_plugopt_without_spaces_still_parses(self): + self.set_tunables(['mock.baz=True']) + self.assertIs(self.plugin.options['baz'].value, True) + + def test_plugopt_with_spaces_around_equals(self): + self.set_tunables(['mock.baz = True']) + self.assertIs(self.plugin.options['baz'].value, True) + + def test_plugopt_with_spaces_disables_option(self): + self.set_tunables(['mock.baz = False']) + self.assertIs(self.plugin.options['baz'].value, False) + + def test_plugopt_string_value_with_spaces_around_equals(self): + self.set_tunables(['mock.test_option = bar']) + self.assertEqual(self.plugin.options['test_option'].value, + 'bar') + + def test_unknown_plugopt_exits_even_with_spaces(self): + # An unknown option name (with surrounding whitespace) should still + # be reported as unknown rather than silently mis-stripped. + with self.assertRaises(SystemExit): + self.set_tunables(['mock.bogus = True']) + + if __name__ == "__main__": unittest.main()